<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Mathieu Agopian</title><link href="//mathieu.agopian.info/blog/" rel="alternate"></link><link href="//mathieu.agopian.info/blog/feeds/all.atom.xml" rel="self"></link><id>//mathieu.agopian.info/blog/</id><updated>2019-11-10T16:17:00+01:00</updated><entry><title>Storing credentials or tokens on the frontend</title><link href="//mathieu.agopian.info/blog/storing-credentials-or-tokens-on-the-frontend.html" rel="alternate"></link><published>2019-11-10T16:17:00+01:00</published><updated>2019-11-10T16:17:00+01:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2019-11-10:/blog/storing-credentials-or-tokens-on-the-frontend.html</id><summary type="html">&lt;p&gt;Security is hard.
Also, I'm not a security expert, so please take what you read here with a grain of salt, and let me know if there's anything wrong.&lt;/p&gt;
&lt;p&gt;That being out of the way: I went down a rabbit hole lately, when someone told me: "we worked hard to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Security is hard.
Also, I'm not a security expert, so please take what you read here with a grain of salt, and let me know if there's anything wrong.&lt;/p&gt;
&lt;p&gt;That being out of the way: I went down a rabbit hole lately, when someone told me: "we worked hard to store the auth token in cookies for our frontend, because we previously stored it in localStorage, and it's very insecure".&lt;/p&gt;
&lt;p&gt;Indeed a search for &lt;a href="https://www.google.com/search?q=localstorage+insecure"&gt;"localStorage insecure"&lt;/a&gt; will return many results.&lt;/p&gt;
&lt;p&gt;But then, is storing sensitive data in cookies, like we did in the old days, more secure?&lt;/p&gt;
&lt;p&gt;Say you're developing a frontend for some API, or an SPA, and you need to authenticate a user.
Let's break things down, and try to understand what are the risks, what are the tools at our disposal as web developers, and what are some of the solutions.&lt;/p&gt;
&lt;h2&gt;Attacks and vulnerabilities&lt;/h2&gt;
&lt;p&gt;There are two main ways to attack a website:&lt;/p&gt;
&lt;h3&gt;CSRF&lt;/h3&gt;
&lt;p&gt;A &lt;a href="https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)"&gt;Cross Site Request Forgery&lt;/a&gt; is a very easy attack that works against cookies: a browser willingly attaches cookies to a request that goes to the associated website.&lt;/p&gt;
&lt;p&gt;Say I have a cookie created by &lt;code&gt;https://my.bank.com&lt;/code&gt; with my credentials.
If I end up being tricked to follow a link to &lt;code&gt;http://bad.com/evil_csrf_fake_form&lt;/code&gt; that displays a form looking like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://my.bank.com/account/transfer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hidden&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;to&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;evil_attacker&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hidden&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;amount&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;123456&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;submit&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Click here for a chance of winning a free gift!&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The browser will happily attach the cookie from &lt;code&gt;https://my.bank.com&lt;/code&gt; to the request, from any website or form. The backend, upon receiving the request with the attached valid credentials, will happily transfer the money.&lt;/p&gt;
&lt;h3&gt;XSS&lt;/h3&gt;
&lt;p&gt;A &lt;a href="https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)"&gt;Cross Site Scripting&lt;/a&gt; attack is usually a bit more elaborate, as it needs to inject some javascript in the rendered page on the attacked website.
Once it's done though, it's basically game over for the website, as the attacker will have access to everything the legitimate javascript code has. Including in-memory data, localStorage, cookies without &lt;code&gt;httpOnly&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;An attacker could dump the content of the localStorage using a very generic &lt;code&gt;JSON.stringify(localStorage)&lt;/code&gt;, or target the website specifically and access in-memory data, or forge a fake login form that would look totally legitimate AND be on the legitimate domain.&lt;/p&gt;
&lt;h2&gt;Storage and persistence of sensitive data&lt;/h2&gt;
&lt;p&gt;Nowadays you can store sensitive data like username/password, bearer token, jwt token... either in:&lt;/p&gt;
&lt;h3&gt;Memory&lt;/h3&gt;
&lt;p&gt;Storing in memory means holding the credentials or token in a variable, a redux state, closures or whatever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: this is not vulnerable to CSRF, and an XSS attack will need to be targetted towards your specific way of storing the data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: still vulnerable to a targetted XSS attack, and you're not persisting the credentials or token: if the user opens a new tab or restarts their browser, they'll have to re-authenticate.&lt;/p&gt;
&lt;h3&gt;localStorage or sessionStorage&lt;/h3&gt;
&lt;p&gt;Both are nearly identical, the first one persists accross tabs and browser restarts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: readable by javascript, very convenient, not vulnerable to CSRF attacks. They are both readable by javascript&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: readable in a generic way like &lt;code&gt;JSON.stringify(localStorage)&lt;/code&gt; which makes it very easy for an attacker in case of an XSS&lt;/p&gt;
&lt;h3&gt;Cookies&lt;/h3&gt;
&lt;p&gt;Cookies can be created as &lt;code&gt;httpOnly&lt;/code&gt;, which means the content isn't readable by javascript. It can also be &lt;code&gt;sameSite&lt;/code&gt; to defeat CSRF attacks, but beware that this &lt;a href="https://caniuse.com/#feat=same-site-cookie-attribute"&gt;isn't supported by all the browsers yet&lt;/a&gt;, and is obviously only available if the frontend is served from the same domain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Good&lt;/strong&gt;: the cookie is automatically attached to any request sent by your frontend. Not vulnerable to XSS if &lt;code&gt;httpOnly&lt;/code&gt;. Not vulnerable to CSRF if &lt;code&gt;sameSite&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad&lt;/strong&gt;: Vulnerable to CSRF if you can't use &lt;code&gt;sameSite&lt;/code&gt; because not supported by all the browsers you target, or the frontend is not on the same domain or you're using an auth solution like &lt;a href="https://auth0.com/"&gt;auth0&lt;/a&gt; or &lt;a href="https://openid.net/"&gt;openID&lt;/a&gt; that you don't control.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;h3&gt;Frontend and backend on the same domain&lt;/h3&gt;
&lt;p&gt;You control the backend, and the frontend is on the same domain. You're thus only vulnerable to XSS (but then again, an XSS is game over). To minimize the risks you might prefer staying away from local or session storage. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the backend stores and reads the session identifier in an &lt;code&gt;httpOnly&lt;/code&gt; and &lt;code&gt;sameSite&lt;/code&gt; cookie, and you can even add some &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern"&gt;csrf tokens&lt;/a&gt; to the mix&lt;/li&gt;
&lt;li&gt;the frontend doesn't have to concern itself with the credentials&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If there is an XSS in your frontend, once you fixed it you can remove the open sessions on the backend. Everybody will need to re-authenticate, and the attacker will have lost all privileges. Also make sure they didn't create an admin user in the meantime!&lt;/p&gt;
&lt;h3&gt;No control over the backend&lt;/h3&gt;
&lt;p&gt;Also applies if the backend is on another domain. In such a case, you can't use &lt;code&gt;sameSite&lt;/code&gt;, and the backend might not support CSRF tokens (eg if it uses stateless authentication).&lt;/p&gt;
&lt;p&gt;Let's take a concrete example (using &lt;a href="https://hasura.io/"&gt;hasura&lt;/a&gt; and a JWT token) which happens to be the place where I found out about the &lt;a href="https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/#refresh_token_persistance"&gt;perfect solution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A JWT token is a very sensitive piece of data: as long as it isn't expired it will be accepted. Even if you discovered an XSS on your frontend and fixed it, if the attacker has a victim's JWT token, they'll still be able to use it. There's no easy way to revoke a JWT token (see &lt;a href="https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/#logout_token_invalidation"&gt;token invalidation&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;So you need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;store the JWT token somewhere safe (not in localStorage, not in a cookie as we can't make it &lt;code&gt;sameSite&lt;/code&gt; and would thus be vulnerable to CSRF attacks)&lt;/li&gt;
&lt;li&gt;make it expire very quickly (say 15 minutes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means that a user will have to re-authenticate every 15 minutes, and every new tab or browser restart. Which is obviously a pain.&lt;/p&gt;
&lt;p&gt;So here comes &lt;a href="https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/#silent_refresh"&gt;the refresh token&lt;/a&gt; to the rescue!&lt;/p&gt;
&lt;p&gt;This refresh token is stored in a &lt;code&gt;httpOnly&lt;/code&gt; cookie: whenever the in-memory JWT token is about to expire (or has expired), the frontend can make a request to some &lt;code&gt;/authorize&lt;/code&gt; endpoint that will return the new JWT token. The frontend can then store this new short lived token in memory.&lt;/p&gt;
&lt;p&gt;It's safe to have this refresh token in a cookie because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it won't be readable by javascript, so not vulnerable to an XSS attack&lt;/li&gt;
&lt;li&gt;if attached to a CSRF request, it'll only refresh the tokens (in memory in the user's browser)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, do we have the perfect solution with in-memory short lived sensitive data and a refresh token in an &lt;code&gt;httpOnly&lt;/code&gt; cookie?
Sure, if you're willing (and able) to set it up. It does mean that the auth backend has to support this flow (I believe it's not the case for &lt;a href="https://auth0.com/docs/flows/concepts/implicit#spas-and-refresh-tokens"&gt;auth0&lt;/a&gt; as they recommend using &lt;a href="https://auth0.com/docs/api-auth/tutorials/silent-authentication"&gt;silent authentication&lt;/a&gt; which still has the limitations of in-memory: no persistence accross tabs/restarts).
It does add an extra layer of complexity compared to a simple solution like persisting the session in localStorage. And it's not a silver bullet in case of an XSS.&lt;/p&gt;
&lt;p&gt;You might consider that if an XSS is game over, then you might as well use localStorage which is way more convenient. But then, it's not because a dedicated thief can break any lock that you leave your door open when you leave the house, right? It's a matter of mitigation and reducing the attack surface.&lt;/p&gt;
&lt;p&gt;If you don't have anything of value in your house, you might choose to close the door, and leave the keys under the welcome mat for convenience. How did you like my analogy of using localStorage? ;)&lt;/p&gt;
&lt;p&gt;By the way, if the "XSS is basically game over" sounds scary, know that there are ways to mitigate those, see an interesting article &lt;a href="https://github.blog/2016-04-12-githubs-csp-journey/"&gt;Github CSP journey&lt;/a&gt;, and &lt;a href="https://github.com/cure53/XSSChallengeWiki/wiki/H5SC-Minichallenge-3:-%22Sh*t,-it%27s-CSP!%22"&gt;a challenge&lt;/a&gt; to learn a bit more about this subject.&lt;/p&gt;</content><category term="misc"></category><category term="security"></category><category term="web"></category></entry><entry><title>Making a pong game in elm (4)</title><link href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-4.html" rel="alternate"></link><published>2019-08-12T11:36:00+02:00</published><updated>2019-08-12T11:36:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2019-08-12:/blog/making-a-pong-game-in-elm-4.html</id><summary type="html">&lt;p&gt;Following the &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm.html"&gt;three&lt;/a&gt;
&lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-2.html"&gt;previous&lt;/a&gt;
&lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-3.html"&gt;blog&lt;/a&gt; posts, let's continue taking
tiny steps in our endeavour to create a pong game in elm.&lt;/p&gt;
&lt;h2&gt;Contribution from &lt;a href="https://github.com/Natim"&gt;Rémy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rémy is a former colleague from
&lt;a href="https://mozilla.org"&gt;Mozilla&lt;/a&gt; and is a wonderful, very
joyful and productive friend. When he saw this series of blog posts, he
contributed …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Following the &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm.html"&gt;three&lt;/a&gt;
&lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-2.html"&gt;previous&lt;/a&gt;
&lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-3.html"&gt;blog&lt;/a&gt; posts, let's continue taking
tiny steps in our endeavour to create a pong game in elm.&lt;/p&gt;
&lt;h2&gt;Contribution from &lt;a href="https://github.com/Natim"&gt;Rémy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rémy is a former colleague from
&lt;a href="https://mozilla.org"&gt;Mozilla&lt;/a&gt; and is a wonderful, very
joyful and productive friend. When he saw this series of blog posts, he
contributed a few
&lt;a href="https://github.com/magopian/elm-pong/issues?q=is%3Aissue+author%3ANatim"&gt;issues&lt;/a&gt;
and
&lt;a href="https://github.com/magopian/elm-pong/pulls?q=is%3Apr+author%3ANatim"&gt;pull requests&lt;/a&gt;
to the project, thanks!&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/magopian/elm-pong/pull/1/files"&gt;first PR&lt;/a&gt; is to fix a
corner case where the ball would be "trapped" by the paddle: if the paddle
catches the ball "too late" but still before it touches the side, the ball
bounces back and forth on each frame. To fix that, the trick is to also check
the direction the ball is going, and only bounce if it's going towards the side
the paddle is on:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;             (ball.x - ball.radius &amp;lt;= x + width)
                 &amp;amp;&amp;amp; (ball.y &amp;gt;= y)
                 &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)
&lt;span class="gi"&gt;+                &amp;amp;&amp;amp; (ball.horizSpeed &amp;lt; 0)&lt;/span&gt;

         RightPaddle { x, y, height } -&amp;gt;
             (ball.x + ball.radius &amp;gt;= x)
                 &amp;amp;&amp;amp; (ball.y &amp;gt;= y)
                 &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)
&lt;span class="gi"&gt;+                &amp;amp;&amp;amp; (ball.horizSpeed &amp;gt; 0)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/dc535518fa478a7ff403f8e792b336c5bbea0209"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/magopian/elm-pong/pull/2/files"&gt;second PR&lt;/a&gt; is a bit
more involved: it's not related to the game itself, but rather to how people
can interact and contribute to it.&lt;/p&gt;
&lt;p&gt;Adding a few helper scripts is convenient to newcomers, and having a &lt;code&gt;npm run
deploy&lt;/code&gt; helps with automating the upload to the
&lt;a href="https://magopian.github.io/elm-pong/"&gt;github pages&lt;/a&gt; where the most up to date
version is playable.&lt;/p&gt;
&lt;p&gt;Now for the issues. The
&lt;a href="https://github.com/magopian/elm-pong/issues/4"&gt;second one&lt;/a&gt; is an issue I
spotted but didn't get around to fixing yet. Here's how to reproduce it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one player presses and keeps pressing a key (eg: the down arrow)&lt;/li&gt;
&lt;li&gt;as the paddle moves down, the game is restarted (because one of the two
  players won)&lt;/li&gt;
&lt;li&gt;while the game is paused (the 500ms delay), the key is released (eg: the down
  arrow is released)&lt;/li&gt;
&lt;li&gt;once the game is restarted, the paddle moves down on its own, even though the
  down arrow is released&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reason is pretty obvious: during the pause we're not subscribed to the
&lt;code&gt;onKeyUp&lt;/code&gt; events so we don't update the &lt;code&gt;PaddleMovement&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There's an easy way to fix that: reset the paddles (position and movement) once
the game restarts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         SleepDone -&amp;gt;
             ( { model
                 | ball = initBall
&lt;span class="gi"&gt;+                , rightPaddle = RightPaddle &amp;lt;| initPaddle 480&lt;/span&gt;
&lt;span class="gi"&gt;+                , leftPaddle = LeftPaddle &amp;lt;| initPaddle 10&lt;/span&gt;
&lt;span class="gi"&gt;+                , rightPaddleMovement = NotMoving&lt;/span&gt;
&lt;span class="gi"&gt;+                , leftPaddleMovement = NotMoving&lt;/span&gt;
                 , gameStatus = NoWinner
               }
             , Cmd.none
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/661a31e4bf082cae5c74aa1840541933c5e4a22a"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another way would have been to keep being subscribed to the key events even
during the pause, but that wouldn't have been sufficient: during the pause we
don't subscribe to the animation frame events anymore, and as such our "game
loop" is on pause, and we don't update anything anymore. So even though we'd be
registering the key presses, the paddles wouldn't be moving, which would be
confusing.&lt;/p&gt;
&lt;p&gt;So instead of changing the subscription, we'd have to do a &lt;code&gt;case&lt;/code&gt; on the
&lt;code&gt;GameStatus&lt;/code&gt; when updating the ball, and only update it while there's no
winner:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                         ball.vertSpeed

                 updatedBall =
&lt;span class="gd"&gt;-                    { ball&lt;/span&gt;
&lt;span class="gd"&gt;-                        | x = ball.x + horizSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                        , y = ball.y + vertSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                        , horizSpeed = horizSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                        , vertSpeed = vertSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                    }&lt;/span&gt;
&lt;span class="gi"&gt;+                    case model.gameStatus of&lt;/span&gt;
&lt;span class="gi"&gt;+                        Winner _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                            ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                        NoWinner -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                            { ball&lt;/span&gt;
&lt;span class="gi"&gt;+                                | x = ball.x + horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                                , y = ball.y + vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                                , horizSpeed = horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                                , vertSpeed = vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                            }&lt;/span&gt;

                 updatedRightPaddle =
                     updatePaddle model.rightPaddleMovement model.rightPaddle
&lt;span class="gu"&gt;@@ -248,10 +253,6 @@ update msg model =&lt;/span&gt;
         SleepDone -&amp;gt;
             ( { model
                 | ball = initBall
&lt;span class="gd"&gt;-                , rightPaddle = RightPaddle &amp;lt;| initPaddle 480&lt;/span&gt;
&lt;span class="gd"&gt;-                , leftPaddle = LeftPaddle &amp;lt;| initPaddle 10&lt;/span&gt;
&lt;span class="gd"&gt;-                , rightPaddleMovement = NotMoving&lt;/span&gt;
&lt;span class="gd"&gt;-                , leftPaddleMovement = NotMoving&lt;/span&gt;
                 , gameStatus = NoWinner
               }
             , Cmd.none
&lt;span class="gu"&gt;@@ -397,16 +398,11 @@ viewScore score =&lt;/span&gt;

 subscriptions : Model -&amp;gt; Sub Msg
 subscriptions model =
&lt;span class="gd"&gt;-    case model.gameStatus of&lt;/span&gt;
&lt;span class="gd"&gt;-        NoWinner -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            Sub.batch&lt;/span&gt;
&lt;span class="gd"&gt;-                [ Browser.Events.onAnimationFrameDelta OnAnimationFrame&lt;/span&gt;
&lt;span class="gd"&gt;-                , Browser.Events.onKeyDown (Decode.map KeyDown keyDecoder)&lt;/span&gt;
&lt;span class="gd"&gt;-                , Browser.Events.onKeyUp (Decode.map KeyUp keyDecoder)&lt;/span&gt;
&lt;span class="gd"&gt;-                ]&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-        Winner _ -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            Sub.none&lt;/span&gt;
&lt;span class="gi"&gt;+    Sub.batch&lt;/span&gt;
&lt;span class="gi"&gt;+        [ Browser.Events.onAnimationFrameDelta OnAnimationFrame&lt;/span&gt;
&lt;span class="gi"&gt;+        , Browser.Events.onKeyDown (Decode.map KeyDown keyDecoder)&lt;/span&gt;
&lt;span class="gi"&gt;+        , Browser.Events.onKeyUp (Decode.map KeyUp keyDecoder)&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/194042eb8130d08fd25c989bfdfe92bc37a08975"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So we reverted our change in the &lt;code&gt;SleepDone&lt;/code&gt; message (when we restart the
game), and we now always subscribe to all the events (animation frame and
keys). And finally we only update the ball when it's not on pause after a win.&lt;/p&gt;
&lt;p&gt;However we now have a very weird behaviour:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Weird behavior on reset" src="//mathieu.agopian.info/blog/images/elm-pong_weird_reset.gif"&gt;&lt;/p&gt;
&lt;p&gt;Well... the game loop runs every animation frame (so roughly 60 times per
second). And on each frame, even when the game is "paused" (actually, just the
ball is paused now) we check if there's a win, and we increase the score, and then
start a 500ms delay, but this delay doesn't prevent the score increase on the
next frame as there's still a win (the ball hasn't moved).&lt;/p&gt;
&lt;p&gt;So let's modify the current &lt;code&gt;case maybeWinner updatedBall&lt;/code&gt; to be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                     updatePaddle model.leftPaddleMovement model.leftPaddle

                 ( gameStatus, score, cmd ) =
&lt;span class="gd"&gt;-                    case maybeWinner updatedBall of&lt;/span&gt;
&lt;span class="gd"&gt;-                        Nothing -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-                            ( NoWinner, model.score, Cmd.none )&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                        Just player -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    case ( maybeWinner updatedBall, model.gameStatus ) of&lt;/span&gt;
&lt;span class="gi"&gt;+                        ( Just player, NoWinner ) -&amp;gt;&lt;/span&gt;
                             let
                                 alwaysSleepDone : a -&amp;gt; Msg
                                 alwaysSleepDone =
&lt;span class="gu"&gt;@@ -195,6 +192,9 @@ update msg model =&lt;/span&gt;
                                     updateScores model.score player
                             in
                             ( Winner player, updatedScore, delayCmd )
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                        _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                            ( model.gameStatus, model.score, Cmd.none )&lt;/span&gt;
             in
             ( { model
                 | ball = updatedBall
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/c9cc341ce66ff28c0922a9a2c04ad70569fa939b"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here the &lt;code&gt;case&lt;/code&gt; is on a 2-tuple, and the only special case that interests us is
when we have no winner in the &lt;code&gt;GameStatus&lt;/code&gt; yet, but we just detected there's a
win. In this case, and only in this case do we increase the score and start a
500ms sleep.
In all the other cases (destructured as &lt;code&gt;_ -&amp;gt;&lt;/code&gt; here) we just return the
unmodified game status, score, and no commands.&lt;/p&gt;
&lt;p&gt;That's now working perfectly. However the code is starting to get really
unreadable in this game loop... Let's see if we can rewrite and refactor it to
make it clearer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     case msg of
         OnAnimationFrame timeDelta -&amp;gt;
             let
&lt;span class="gd"&gt;-                ball =&lt;/span&gt;
&lt;span class="gd"&gt;-                    model.ball&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                shouldBounce =&lt;/span&gt;
&lt;span class="gd"&gt;-                    shouldBallBounce model.rightPaddle model.ball&lt;/span&gt;
&lt;span class="gd"&gt;-                        || shouldBallBounce model.leftPaddle model.ball&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                horizSpeed =&lt;/span&gt;
&lt;span class="gd"&gt;-                    if shouldBounce then&lt;/span&gt;
&lt;span class="gd"&gt;-                        ball.horizSpeed * -1&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                    else&lt;/span&gt;
&lt;span class="gd"&gt;-                        ball.horizSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                shouldBounceVertically =&lt;/span&gt;
&lt;span class="gd"&gt;-                    shouldBallBounceVertically model.ball&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                vertSpeed =&lt;/span&gt;
&lt;span class="gd"&gt;-                    if shouldBounceVertically then&lt;/span&gt;
&lt;span class="gd"&gt;-                        ball.vertSpeed * -1&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                    else&lt;/span&gt;
&lt;span class="gd"&gt;-                        ball.vertSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
                 updatedBall =
&lt;span class="gd"&gt;-                    case model.gameStatus of&lt;/span&gt;
&lt;span class="gd"&gt;-                        Winner _ -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-                            ball&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                        NoWinner -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-                            { ball&lt;/span&gt;
&lt;span class="gd"&gt;-                                | x = ball.x + horizSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                                , y = ball.y + vertSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                                , horizSpeed = horizSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                                , vertSpeed = vertSpeed&lt;/span&gt;
&lt;span class="gd"&gt;-                            }&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                updatedRightPaddle =&lt;/span&gt;
&lt;span class="gd"&gt;-                    updatePaddle model.rightPaddleMovement model.rightPaddle&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                updatedLeftPaddle =&lt;/span&gt;
&lt;span class="gd"&gt;-                    updatePaddle model.leftPaddleMovement model.leftPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+                    updateBall model&lt;/span&gt;

                 ( gameStatus, score, cmd ) =
                     case ( maybeWinner updatedBall, model.gameStatus ) of
&lt;span class="gu"&gt;@@ -198,8 +158,8 @@ update msg model =&lt;/span&gt;
             in
             ( { model
                 | ball = updatedBall
&lt;span class="gd"&gt;-                , rightPaddle = updatedRightPaddle&lt;/span&gt;
&lt;span class="gd"&gt;-                , leftPaddle = updatedLeftPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+                , rightPaddle = updatePaddle model.rightPaddleMovement model.rightPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+                , leftPaddle = updatePaddle model.leftPaddleMovement model.leftPaddle&lt;/span&gt;
                 , gameStatus = gameStatus
                 , score = score
               }
&lt;span class="gu"&gt;@@ -259,6 +219,50 @@ update msg model =&lt;/span&gt;
             )


&lt;span class="gi"&gt;+updateBall :&lt;/span&gt;
&lt;span class="gi"&gt;+    { a&lt;/span&gt;
&lt;span class="gi"&gt;+        | gameStatus : GameStatus&lt;/span&gt;
&lt;span class="gi"&gt;+        , ball : Ball&lt;/span&gt;
&lt;span class="gi"&gt;+        , rightPaddle : Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+        , leftPaddle : Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+    -&amp;gt; Ball&lt;/span&gt;
&lt;span class="gi"&gt;+updateBall { gameStatus, ball, rightPaddle, leftPaddle } =&lt;/span&gt;
&lt;span class="gi"&gt;+    let&lt;/span&gt;
&lt;span class="gi"&gt;+        shouldBounce =&lt;/span&gt;
&lt;span class="gi"&gt;+            shouldBallBounce rightPaddle ball&lt;/span&gt;
&lt;span class="gi"&gt;+                || shouldBallBounce leftPaddle ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        horizSpeed =&lt;/span&gt;
&lt;span class="gi"&gt;+            if shouldBounce then&lt;/span&gt;
&lt;span class="gi"&gt;+                ball.horizSpeed * -1&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            else&lt;/span&gt;
&lt;span class="gi"&gt;+                ball.horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        shouldBounceVertically =&lt;/span&gt;
&lt;span class="gi"&gt;+            shouldBallBounceVertically ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        vertSpeed =&lt;/span&gt;
&lt;span class="gi"&gt;+            if shouldBounceVertically then&lt;/span&gt;
&lt;span class="gi"&gt;+                ball.vertSpeed * -1&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            else&lt;/span&gt;
&lt;span class="gi"&gt;+                ball.vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+    in&lt;/span&gt;
&lt;span class="gi"&gt;+    case gameStatus of&lt;/span&gt;
&lt;span class="gi"&gt;+        Winner _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        NoWinner -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            { ball&lt;/span&gt;
&lt;span class="gi"&gt;+                | x = ball.x + horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                , y = ball.y + vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                , horizSpeed = horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                , vertSpeed = vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+            }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 updatePaddle : PaddleMovement -&amp;gt; Paddle -&amp;gt; Paddle
 updatePaddle movement paddle =
     let
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/34722dbf26165fec2f9760334b34c0ee57e7afd3"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That's a big one! It looks impressive, but most of it is just moving the code
related to updating the ball (checking if it bounces to update its speed and
moving it) to its own &lt;code&gt;updateBall&lt;/code&gt; helper.&lt;/p&gt;
&lt;p&gt;However there's something worth noting here, and it's in the signature of the
&lt;code&gt;updateBall&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;updateBall&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt;
        &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;gameStatus&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;GameStatus&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ball&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Ball&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rightPaddle&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Paddle&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;leftPaddle&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Paddle&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Ball&lt;/span&gt;
&lt;span class="nv"&gt;updateBall&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;gameStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ball&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;rightPaddle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;leftPaddle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Up til now we've seen how to write a type signature with various types, but
this is the first time we're seeing this &lt;code&gt;{ a | ...}&lt;/code&gt; notation. This is an
&lt;a href="https://elm-lang.org/docs/records#record-types"&gt;extensible record&lt;/a&gt; (more
information in
&lt;a href="https://medium.com/@ckoster22/advanced-types-in-elm-extensible-records-67e9d804030d"&gt;this "Advanced Types in Elm - Extensible Records" blog post by Charlie
Koster&lt;/a&gt;
and the
&lt;a href="https://www.youtube.com/watch?v=DoA4Txr4GUs"&gt;"Scaling elm apps" talk by Richard Feldman&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To sum it up, it's a way to&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;document which fields are of interest to the function&lt;/li&gt;
&lt;li&gt;narrow the arguments to the function: it'll only take, use or return specific fields from the records&lt;/li&gt;
&lt;li&gt;make a function usable on several different types of records, as long as they have the fields defined in the extensible record&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our case we're only using this for the first two use cases.&lt;/p&gt;
&lt;p&gt;We're now left with this game status and score update. Those two should
obviously happen together, but it feels a bit alien to have them mixed in the
game loop together with the updating of the paddles.&lt;/p&gt;
&lt;p&gt;How awesome would it be to have a &lt;code&gt;NewWinner Player&lt;/code&gt; message? If we had it, we
could update the game status and the score update in this &lt;code&gt;case&lt;/code&gt; of the
&lt;code&gt;update&lt;/code&gt; function, and it would make perfect sense!&lt;/p&gt;
&lt;p&gt;"But Mathieu, how do we &lt;em&gt;send&lt;/em&gt; our own messages to the &lt;code&gt;update function&lt;/code&gt;?
Aren't messages usually coming from subscriptions, or maybe events like
&lt;code&gt;onClick&lt;/code&gt; on a button and the like? We've always had those messages handed to
us by the elm runtime through the &lt;code&gt;update&lt;/code&gt; function!".&lt;/p&gt;
&lt;p&gt;Well, yes, usually the messages are provided, relayed by the elm runtime. And
we could tell the runtime to
&lt;a href="http://faq.elm-community.org/#how-do-i-generate-a-new-message-as-a-command"&gt;send us such a message&lt;/a&gt;,
but as you can read from this piece, it's not recommended (check
&lt;a href="https://medium.com/elm-shorts/how-to-turn-a-msg-into-a-cmd-msg-in-elm-5dd095175d84"&gt;"How to turn a Msg into a Cmd in Elm?" from Wouter In t Velt&lt;/a&gt;
for more information on why).&lt;/p&gt;
&lt;p&gt;But if we get back to the original question: how do we send our own messages to
the &lt;code&gt;update&lt;/code&gt; &lt;strong&gt;function&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;Well, the &lt;code&gt;update&lt;/code&gt; function is just that: a function. And a function can be
called, for example with our own &lt;code&gt;NewWinner Player&lt;/code&gt; message, and the updated
model we get in the "game loop":&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -63,6 +63,7 @@ type Msg&lt;/span&gt;
     | KeyDown PlayerAction
     | KeyUp PlayerAction
     | SleepDone
&lt;span class="gi"&gt;+    | NewWinner Player&lt;/span&gt;


 type PlayerAction
&lt;span class="gu"&gt;@@ -136,34 +137,32 @@ update msg model =&lt;/span&gt;
                 updatedBall =
                     updateBall model

&lt;span class="gd"&gt;-                ( gameStatus, score, cmd ) =&lt;/span&gt;
&lt;span class="gd"&gt;-                    case ( maybeWinner updatedBall, model.gameStatus ) of&lt;/span&gt;
&lt;span class="gd"&gt;-                        ( Just player, NoWinner ) -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-                            let&lt;/span&gt;
&lt;span class="gd"&gt;-                                alwaysSleepDone : a -&amp;gt; Msg&lt;/span&gt;
&lt;span class="gd"&gt;-                                alwaysSleepDone =&lt;/span&gt;
&lt;span class="gd"&gt;-                                    always SleepDone&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                                delayCmd =&lt;/span&gt;
&lt;span class="gd"&gt;-                                    Process.sleep 500&lt;/span&gt;
&lt;span class="gd"&gt;-                                        |&amp;gt; Task.perform alwaysSleepDone&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                                updatedScore =&lt;/span&gt;
&lt;span class="gd"&gt;-                                    updateScores model.score player&lt;/span&gt;
&lt;span class="gd"&gt;-                            in&lt;/span&gt;
&lt;span class="gd"&gt;-                            ( Winner player, updatedScore, delayCmd )&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                        _ -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-                            ( model.gameStatus, model.score, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+                updatedModel =&lt;/span&gt;
&lt;span class="gi"&gt;+                    { model&lt;/span&gt;
&lt;span class="gi"&gt;+                        | ball = updatedBall&lt;/span&gt;
&lt;span class="gi"&gt;+                        , rightPaddle = updatePaddle model.rightPaddleMovement model.rightPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+                        , leftPaddle = updatePaddle model.leftPaddleMovement model.leftPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+                    }&lt;/span&gt;
             in
&lt;span class="gd"&gt;-            ( { model&lt;/span&gt;
&lt;span class="gd"&gt;-                | ball = updatedBall&lt;/span&gt;
&lt;span class="gd"&gt;-                , rightPaddle = updatePaddle model.rightPaddleMovement model.rightPaddle&lt;/span&gt;
&lt;span class="gd"&gt;-                , leftPaddle = updatePaddle model.leftPaddleMovement model.leftPaddle&lt;/span&gt;
&lt;span class="gd"&gt;-                , gameStatus = gameStatus&lt;/span&gt;
&lt;span class="gd"&gt;-                , score = score&lt;/span&gt;
&lt;span class="gd"&gt;-              }&lt;/span&gt;
&lt;span class="gd"&gt;-            , cmd&lt;/span&gt;
&lt;span class="gi"&gt;+            case ( maybeWinner updatedBall, model.gameStatus ) of&lt;/span&gt;
&lt;span class="gi"&gt;+                ( Just player, NoWinner ) -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    update (NewWinner player) updatedModel&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( updatedModel, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        NewWinner player -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            let&lt;/span&gt;
&lt;span class="gi"&gt;+                alwaysSleepDone : a -&amp;gt; Msg&lt;/span&gt;
&lt;span class="gi"&gt;+                alwaysSleepDone =&lt;/span&gt;
&lt;span class="gi"&gt;+                    always SleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                updatedScore =&lt;/span&gt;
&lt;span class="gi"&gt;+                    updateScores model.score player&lt;/span&gt;
&lt;span class="gi"&gt;+            in&lt;/span&gt;
&lt;span class="gi"&gt;+            ( { model | gameStatus = Winner player, score = updatedScore }&lt;/span&gt;
&lt;span class="gi"&gt;+            , Process.sleep 500&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; Task.perform alwaysSleepDone&lt;/span&gt;
             )

         KeyDown playerAction -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/abbaf2d90ba81d37158945abf010475e9b301696"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What we did here is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in the game loop, update the ball and the paddles&lt;/li&gt;
&lt;li&gt;make an updated model with those updates&lt;/li&gt;
&lt;li&gt;if there's no new winner, return that updated model&lt;/li&gt;
&lt;li&gt;if there's a new winner, return the result of a call to the &lt;code&gt;update&lt;/code&gt; function
  with our &lt;code&gt;NewWinner Player&lt;/code&gt; message and the updated model&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While we're rewriting and refactoring parts of the &lt;code&gt;update&lt;/code&gt; function, let's
rename this &lt;code&gt;SleepDone&lt;/code&gt; message which isn't very meaningful to &lt;code&gt;RestartGame&lt;/code&gt;
instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     = OnAnimationFrame Float
     | KeyDown PlayerAction
     | KeyUp PlayerAction
&lt;span class="gd"&gt;-    | SleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+    | RestartGame&lt;/span&gt;
     | NewWinner Player


&lt;span class="gu"&gt;@@ -153,16 +153,16 @@ update msg model =&lt;/span&gt;

         NewWinner player -&amp;gt;
             let
&lt;span class="gd"&gt;-                alwaysSleepDone : a -&amp;gt; Msg&lt;/span&gt;
&lt;span class="gd"&gt;-                alwaysSleepDone =&lt;/span&gt;
&lt;span class="gd"&gt;-                    always SleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+                alwaysRestartGame : a -&amp;gt; Msg&lt;/span&gt;
&lt;span class="gi"&gt;+                alwaysRestartGame =&lt;/span&gt;
&lt;span class="gi"&gt;+                    always RestartGame&lt;/span&gt;

                 updatedScore =
                     updateScores model.score player
             in
             ( { model | gameStatus = Winner player, score = updatedScore }
             , Process.sleep 500
&lt;span class="gd"&gt;-                |&amp;gt; Task.perform alwaysSleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; Task.perform alwaysRestartGame&lt;/span&gt;
             )

         KeyDown playerAction -&amp;gt;
&lt;span class="gu"&gt;@@ -209,7 +209,7 @@ update msg model =&lt;/span&gt;
                     , Cmd.none
                     )

&lt;span class="gd"&gt;-        SleepDone -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+        RestartGame -&amp;gt;&lt;/span&gt;
             ( { model
                 | ball = initBall
                 , gameStatus = NoWinner
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/b88ffa27688628881cc89a96a5034deb7224d6f8"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/12-external-contributions"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We're now in a much better place!&lt;/p&gt;
&lt;h2&gt;Non linear paddles&lt;/h2&gt;
&lt;p&gt;If we look back at the
&lt;a href="https://www.youtube.com/watch?v=it0sf4CMDeM"&gt;original gameplay&lt;/a&gt; we see that
the ball doesn't bounce linearily on the paddles. Depending on where it touches
the paddle, the bounce angle varies. This is better explained in
&lt;a href="https://noobtuts.com/unity/2d-pong-game"&gt;this tutorial on writing a pong game in unity&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's implement that!&lt;/p&gt;
&lt;p&gt;When we update the &lt;code&gt;horizSpeed&lt;/code&gt; when the ball &lt;code&gt;shouldBounce&lt;/code&gt;, we now also need
to update the &lt;code&gt;vertSpeed&lt;/code&gt; according to the place where the ball hit the paddle.&lt;/p&gt;
&lt;p&gt;The idea is to come up with a "distance" in signed percentage from the center
of the paddle, so we would have -100% for a hit right on the top of the paddle,
and 100% if it's the bottom of the paddle.&lt;/p&gt;
&lt;p&gt;Once we have this percentage, we can divide it by 10 to have a vertical speed
between 0 (the ball hits the paddle right in the center) and 10 (the ball hits
the top or the bottom of the paddle).&lt;/p&gt;
&lt;p&gt;Here's the modified &lt;code&gt;updateBall&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; updateBall { gameStatus, ball, rightPaddle, leftPaddle } =
     let
&lt;span class="gd"&gt;-        shouldBounce =&lt;/span&gt;
&lt;span class="gd"&gt;-            shouldBallBounce rightPaddle ball&lt;/span&gt;
&lt;span class="gd"&gt;-                || shouldBallBounce leftPaddle ball&lt;/span&gt;
&lt;span class="gi"&gt;+        maybeRightDistance =&lt;/span&gt;
&lt;span class="gi"&gt;+            maybeBounceDistanceFromCenter rightPaddle ball&lt;/span&gt;

&lt;span class="gd"&gt;-        horizSpeed =&lt;/span&gt;
&lt;span class="gd"&gt;-            if shouldBounce then&lt;/span&gt;
&lt;span class="gd"&gt;-                ball.horizSpeed * -1&lt;/span&gt;
&lt;span class="gi"&gt;+        maybeLeftDistance =&lt;/span&gt;
&lt;span class="gi"&gt;+            maybeBounceDistanceFromCenter leftPaddle ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        maybeDistance =&lt;/span&gt;
&lt;span class="gi"&gt;+            -- Combine the two maybes and keep the one that isn&amp;#39;t Nothing, if any.&lt;/span&gt;
&lt;span class="gi"&gt;+            if maybeRightDistance == Nothing then&lt;/span&gt;
&lt;span class="gi"&gt;+                maybeLeftDistance&lt;/span&gt;

             else
&lt;span class="gd"&gt;-                ball.horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                maybeRightDistance&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        ( horizSpeed, bouncedVertSpeed ) =&lt;/span&gt;
&lt;span class="gi"&gt;+            case maybeDistance of&lt;/span&gt;
&lt;span class="gi"&gt;+                Nothing -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    -- No bounce&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( ball.horizSpeed, ball.vertSpeed )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                Just distance -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( ball.horizSpeed * -1&lt;/span&gt;
&lt;span class="gi"&gt;+                    , distance // 10&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;

         shouldBounceVertically =
             shouldBallBounceVertically ball

         vertSpeed =
             if shouldBounceVertically then
&lt;span class="gd"&gt;-                ball.vertSpeed * -1&lt;/span&gt;
&lt;span class="gi"&gt;+                bouncedVertSpeed * -1&lt;/span&gt;

             else
&lt;span class="gd"&gt;-                ball.vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                bouncedVertSpeed&lt;/span&gt;
     in
     case gameStatus of
         Winner _ -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nothing fancy here appart from the &lt;code&gt;maybeDistance&lt;/code&gt;: we "combine" both &lt;code&gt;Maybe&lt;/code&gt;s
by keeping the first one that isn't a &lt;code&gt;Nothing&lt;/code&gt; if any. This way we simplify
the &lt;code&gt;case maybeDistance&lt;/code&gt; and we don't have to duplicate the code that updates
the &lt;code&gt;horizSpeed&lt;/code&gt; and &lt;code&gt;vertSpeed&lt;/code&gt; if there's a hit on the left or right paddle.&lt;/p&gt;
&lt;p&gt;Now for the hairy &lt;code&gt;maybeBounceDistanceFromCenter&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-shouldBallBounce : Paddle -&amp;gt; Ball -&amp;gt; Bool&lt;/span&gt;
&lt;span class="gd"&gt;-shouldBallBounce paddle ball =&lt;/span&gt;
&lt;span class="gi"&gt;+maybeBounceDistanceFromCenter : Paddle -&amp;gt; Ball -&amp;gt; Maybe Int&lt;/span&gt;
&lt;span class="gi"&gt;+maybeBounceDistanceFromCenter paddle ball =&lt;/span&gt;
&lt;span class="gi"&gt;+    -- If the ball bounces, return Just the distance from the paddle center in&lt;/span&gt;
&lt;span class="gi"&gt;+    -- percentage, so -100% if it&amp;#39;s the very top of the paddle, 100% if it&amp;#39;s&lt;/span&gt;
&lt;span class="gi"&gt;+    -- the very bottom of the paddle.&lt;/span&gt;
&lt;span class="gi"&gt;+    let&lt;/span&gt;
&lt;span class="gi"&gt;+        normalize : Int -&amp;gt; Int -&amp;gt; Int&lt;/span&gt;
&lt;span class="gi"&gt;+        normalize distance height =&lt;/span&gt;
&lt;span class="gi"&gt;+            (distance - (height // 2)) * 100 // (height // 2)&lt;/span&gt;
&lt;span class="gi"&gt;+    in&lt;/span&gt;
     case paddle of
         LeftPaddle { x, y, width, height } -&amp;gt;
&lt;span class="gd"&gt;-            (ball.x - ball.radius &amp;lt;= x + width)&lt;/span&gt;
&lt;span class="gd"&gt;-                &amp;amp;&amp;amp; (ball.y &amp;gt;= y)&lt;/span&gt;
&lt;span class="gd"&gt;-                &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)&lt;/span&gt;
&lt;span class="gd"&gt;-                &amp;amp;&amp;amp; (ball.horizSpeed &amp;lt; 0)&lt;/span&gt;
&lt;span class="gi"&gt;+            if&lt;/span&gt;
&lt;span class="gi"&gt;+                (ball.x - ball.radius &amp;lt;= x + width)&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;amp;&amp;amp; (ball.y &amp;gt;= y)&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;amp;&amp;amp; (ball.horizSpeed &amp;lt; 0)&lt;/span&gt;
&lt;span class="gi"&gt;+            then&lt;/span&gt;
&lt;span class="gi"&gt;+                Just &amp;lt;| normalize (ball.y - y) height&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            else&lt;/span&gt;
&lt;span class="gi"&gt;+                Nothing&lt;/span&gt;

         RightPaddle { x, y, height } -&amp;gt;
&lt;span class="gd"&gt;-            (ball.x + ball.radius &amp;gt;= x)&lt;/span&gt;
&lt;span class="gd"&gt;-                &amp;amp;&amp;amp; (ball.y &amp;gt;= y)&lt;/span&gt;
&lt;span class="gd"&gt;-                &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)&lt;/span&gt;
&lt;span class="gd"&gt;-                &amp;amp;&amp;amp; (ball.horizSpeed &amp;gt; 0)&lt;/span&gt;
&lt;span class="gi"&gt;+            if&lt;/span&gt;
&lt;span class="gi"&gt;+                (ball.x + ball.radius &amp;gt;= x)&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;amp;&amp;amp; (ball.y &amp;gt;= y)&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;amp;&amp;amp; (ball.horizSpeed &amp;gt; 0)&lt;/span&gt;
&lt;span class="gi"&gt;+            then&lt;/span&gt;
&lt;span class="gi"&gt;+                Just &amp;lt;| normalize (ball.y - y) height&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+            else&lt;/span&gt;
&lt;span class="gi"&gt;+                Nothing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/434ea02cd02e87897656ef448c7814a6f606ffc6"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Phew, that was a tough one, but we did it!&lt;/p&gt;
&lt;h2&gt;Cosmetic changes&lt;/h2&gt;
&lt;p&gt;Well, it seems we're nearly done! By looking at the original pong we can see
there's a couple of obvious differences still: the ball should be square (which
will make &lt;a href="https://github.com/magopian/elm-pong/issues/3"&gt;this issue&lt;/a&gt;
disappear), and there's a dotted line in the center of the screen.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -23,7 +23,7 @@ type alias Model =&lt;/span&gt;
 type alias Ball =
     { x : Int
     , y : Int
&lt;span class="gd"&gt;-    , radius : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    , size : Int&lt;/span&gt;
     , horizSpeed : Int
     , vertSpeed : Int
     }
&lt;span class="gu"&gt;@@ -104,7 +104,7 @@ initBall : Ball&lt;/span&gt;
 initBall =
     { x = 250
     , y = 250
&lt;span class="gd"&gt;-    , radius = 10&lt;/span&gt;
&lt;span class="gi"&gt;+    , size = 10&lt;/span&gt;
     , horizSpeed = 4
     , vertSpeed = 2
     }
&lt;span class="gu"&gt;@@ -321,7 +321,7 @@ maybeBounceDistanceFromCenter paddle ball =&lt;/span&gt;
     case paddle of
         LeftPaddle { x, y, width, height } -&amp;gt;
             if
&lt;span class="gd"&gt;-                (ball.x - ball.radius &amp;lt;= x + width)&lt;/span&gt;
&lt;span class="gi"&gt;+                (ball.x &amp;lt;= x + width)&lt;/span&gt;
                     &amp;amp;&amp;amp; (ball.y &amp;gt;= y)
                     &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)
                     &amp;amp;&amp;amp; (ball.horizSpeed &amp;lt; 0)
&lt;span class="gu"&gt;@@ -333,7 +333,7 @@ maybeBounceDistanceFromCenter paddle ball =&lt;/span&gt;

         RightPaddle { x, y, height } -&amp;gt;
             if
&lt;span class="gd"&gt;-                (ball.x + ball.radius &amp;gt;= x)&lt;/span&gt;
&lt;span class="gi"&gt;+                (ball.x + ball.size &amp;gt;= x)&lt;/span&gt;
                     &amp;amp;&amp;amp; (ball.y &amp;gt;= y)
                     &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)
                     &amp;amp;&amp;amp; (ball.horizSpeed &amp;gt; 0)
&lt;span class="gu"&gt;@@ -347,18 +347,18 @@ maybeBounceDistanceFromCenter paddle ball =&lt;/span&gt;
 shouldBallBounceVertically : Ball -&amp;gt; Bool
 shouldBallBounceVertically ball =
     let
&lt;span class="gd"&gt;-        radius =&lt;/span&gt;
&lt;span class="gd"&gt;-            ball.radius&lt;/span&gt;
&lt;span class="gi"&gt;+        size =&lt;/span&gt;
&lt;span class="gi"&gt;+            ball.size&lt;/span&gt;
     in
&lt;span class="gd"&gt;-    ball.y &amp;lt;= radius || ball.y &amp;gt;= (500 - radius)&lt;/span&gt;
&lt;span class="gi"&gt;+    ball.y &amp;lt;= size || ball.y &amp;gt;= (500 - size)&lt;/span&gt;


 maybeWinner : Ball -&amp;gt; Maybe Player
 maybeWinner ball =
&lt;span class="gd"&gt;-    if ball.x &amp;lt;= ball.radius then&lt;/span&gt;
&lt;span class="gi"&gt;+    if ball.x &amp;lt;= ball.size then&lt;/span&gt;
         Just RightPlayer

&lt;span class="gd"&gt;-    else if ball.x &amp;gt;= (500 - ball.radius) then&lt;/span&gt;
&lt;span class="gi"&gt;+    else if ball.x &amp;gt;= (500 - ball.size) then&lt;/span&gt;
         Just LeftPlayer

     else
&lt;span class="gu"&gt;@@ -391,11 +391,12 @@ view { ball, rightPaddle, leftPaddle, score } =&lt;/span&gt;


 viewBall : Ball -&amp;gt; Svg.Svg Msg
&lt;span class="gd"&gt;-viewBall { x, y, radius } =&lt;/span&gt;
&lt;span class="gd"&gt;-    circle&lt;/span&gt;
&lt;span class="gd"&gt;-        [ cx &amp;lt;| String.fromInt x&lt;/span&gt;
&lt;span class="gd"&gt;-        , cy &amp;lt;| String.fromInt y&lt;/span&gt;
&lt;span class="gd"&gt;-        , r &amp;lt;| String.fromInt radius&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall ball =&lt;/span&gt;
&lt;span class="gi"&gt;+    rect&lt;/span&gt;
&lt;span class="gi"&gt;+        [ x &amp;lt;| String.fromInt ball.x&lt;/span&gt;
&lt;span class="gi"&gt;+        , y &amp;lt;| String.fromInt ball.y&lt;/span&gt;
&lt;span class="gi"&gt;+        , width &amp;lt;| String.fromInt ball.size&lt;/span&gt;
&lt;span class="gi"&gt;+        , height &amp;lt;| String.fromInt ball.size&lt;/span&gt;
         ]
         []
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/869c3592958baf5573c86039228b88f932ebce42"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We renamed the &lt;code&gt;radius&lt;/code&gt; field of the &lt;code&gt;Ball&lt;/code&gt; record to be &lt;code&gt;size&lt;/code&gt; instead which
makes more sense for a square ball, and updated the &lt;code&gt;viewBall&lt;/code&gt; helper to draw a
rect instead of a circle. We also took the opportunity to fix a small bug, did
you spot it?&lt;/p&gt;
&lt;p&gt;Yes, we were checking for &lt;code&gt;ball.x - ball.radius&lt;/code&gt; for the left paddle... which
meant we were bouncing the ball from the left paddle 10 pixels too early.&lt;/p&gt;
&lt;p&gt;And for the screen divider:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -383,13 +383,28 @@ view { ball, rightPaddle, leftPaddle, score } =&lt;/span&gt;
         , viewBox &amp;quot;0 0 500 500&amp;quot;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ viewBall ball&lt;/span&gt;
&lt;span class="gi"&gt;+        [ viewDivider&lt;/span&gt;
&lt;span class="gi"&gt;+        , viewBall ball&lt;/span&gt;
         , viewPaddle rightPaddle
         , viewPaddle leftPaddle
         , viewScore score
         ]


&lt;span class="gi"&gt;+viewDivider : Svg.Svg Msg&lt;/span&gt;
&lt;span class="gi"&gt;+viewDivider =&lt;/span&gt;
&lt;span class="gi"&gt;+    line&lt;/span&gt;
&lt;span class="gi"&gt;+        [ x1 &amp;quot;249&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , y1 &amp;quot;0&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , x2 &amp;quot;249&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , y2 &amp;quot;500&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , stroke &amp;quot;black&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , strokeDasharray &amp;quot;4&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , strokeWidth &amp;quot;2&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;span class="gi"&gt;+        []&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 viewBall : Ball -&amp;gt; Svg.Svg Msg
 viewBall ball =
     rect
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/fde14ae537d26073a051e17a3922329f3b2d5fcc"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And a final touch: let's move the scores closer to their edges:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -442,9 +442,9 @@ viewScore score =&lt;/span&gt;
         [ fontSize &amp;quot;100px&amp;quot;
         , fontFamily &amp;quot;monospace&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ text_ [ x &amp;quot;100&amp;quot;, y &amp;quot;100&amp;quot;, textAnchor &amp;quot;start&amp;quot; ]&lt;/span&gt;
&lt;span class="gi"&gt;+        [ text_ [ x &amp;quot;50&amp;quot;, y &amp;quot;100&amp;quot;, textAnchor &amp;quot;start&amp;quot; ]&lt;/span&gt;
             [ text &amp;lt;| String.fromInt score.leftPlayerScore ]
&lt;span class="gd"&gt;-        , text_ [ x &amp;quot;400&amp;quot;, y &amp;quot;100&amp;quot;, textAnchor &amp;quot;end&amp;quot; ]&lt;/span&gt;
&lt;span class="gi"&gt;+        , text_ [ x &amp;quot;450&amp;quot;, y &amp;quot;100&amp;quot;, textAnchor &amp;quot;end&amp;quot; ]&lt;/span&gt;
             [ text &amp;lt;| String.fromInt score.rightPlayerScore ]
         ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/9b904a55cc8257d12a252a8d7e6722ebbc2dfa90"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/13-original-game"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And now we have it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Game that now looks like the original pong" src="//mathieu.agopian.info/blog/images/elm-pong_original_look.png"&gt;&lt;/p&gt;
&lt;h2&gt;Randomizing the game restart&lt;/h2&gt;
&lt;p&gt;Whenever the game (re)starts the ball always has the exact same direction. It
would be nice to have some sort of randomization: in the original gameplay, it
seems that the ball is aimed at the last loser, and the starting height and
direction are random.&lt;/p&gt;
&lt;p&gt;So let's use the
&lt;a href="https://package.elm-lang.org/packages/elm/random/latest/"&gt;elm/random&lt;/a&gt; package!
It should be very straightforward, just calling the equivalent of &lt;code&gt;Math.random&lt;/code&gt;
in javascript right?
&lt;a href="https://package.elm-lang.org/packages/elm/random/latest/#mindset-shift"&gt;Wrong&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I know what you're thinking: "this elm thing is such a downer, always in my
way, always restricting what I can and can't do and all that crap!". And I feel
you. However let's not lose track of the upsides. Every programming language
that I know of is a compromise between upsides and downsides.&lt;/p&gt;
&lt;p&gt;Sure, the pure functional part of elm can be a pain (wait, no side effects? How
are people meant to achieve anything without side effects? In elm the side
effects only happen in the elm runtime), but it's also the best part in my
humble opinion. Not having to worry about hidden side effects is a blessing.&lt;/p&gt;
&lt;p&gt;So anyway, back to the point: getting a random number without an initial seed
is impure (every time you call &lt;code&gt;Math.random()&lt;/code&gt; you have a different result).
And in elm everything is pure. "Pure" meaning that a function call with the
same arguments will always return the exact same result. Which means there's no
side effects.&lt;/p&gt;
&lt;p&gt;With a seed though, it's entirely different: a random number generator will
always give the same result for a given seed, so it's pure.&lt;/p&gt;
&lt;p&gt;So we have two ways to get a random number in elm&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;send a &lt;code&gt;Cmd Msg&lt;/code&gt; to the elm runtime, and "receive" it through a &lt;code&gt;Msg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;query a number directly by providing a seed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's try the first one, and see how it goes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; import Browser.Events
 import Json.Decode as Decode
 import Process
&lt;span class="gi"&gt;+import Random&lt;/span&gt;
 import Svg exposing (..)
 import Svg.Attributes exposing (..)
 import Task
&lt;span class="gu"&gt;@@ -64,6 +65,7 @@ type Msg&lt;/span&gt;
     | KeyUp PlayerAction
     | RestartGame
     | NewWinner Player
&lt;span class="gi"&gt;+    | NewDirection Int&lt;/span&gt;


 type PlayerAction
&lt;span class="gu"&gt;@@ -159,12 +161,25 @@ update msg model =&lt;/span&gt;

                 updatedScore =
                     updateScores model.score player
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                sleepCmd =&lt;/span&gt;
&lt;span class="gi"&gt;+                    Process.sleep 500&lt;/span&gt;
&lt;span class="gi"&gt;+                        |&amp;gt; Task.perform alwaysRestartGame&lt;/span&gt;
             in
             ( { model | gameStatus = Winner player, score = updatedScore }
&lt;span class="gd"&gt;-            , Process.sleep 500&lt;/span&gt;
&lt;span class="gd"&gt;-                |&amp;gt; Task.perform alwaysRestartGame&lt;/span&gt;
&lt;span class="gi"&gt;+            , Cmd.batch&lt;/span&gt;
&lt;span class="gi"&gt;+                [ sleepCmd&lt;/span&gt;
&lt;span class="gi"&gt;+                , Random.generate NewDirection (Random.int 0 100)&lt;/span&gt;
&lt;span class="gi"&gt;+                ]&lt;/span&gt;
             )

&lt;span class="gi"&gt;+        NewDirection direction -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            let&lt;/span&gt;
&lt;span class="gi"&gt;+                _ =&lt;/span&gt;
&lt;span class="gi"&gt;+                    Debug.log &amp;quot;New random direction&amp;quot; direction&lt;/span&gt;
&lt;span class="gi"&gt;+            in&lt;/span&gt;
&lt;span class="gi"&gt;+            ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
         KeyDown playerAction -&amp;gt;
             case playerAction of
                 RightPaddleUp -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Also let's not forget to install the &lt;code&gt;Random&lt;/code&gt; package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ elm install elm/random
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which results in this modification in the &lt;code&gt;elm.json&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;             &amp;quot;elm/core&amp;quot;: &amp;quot;1.0.2&amp;quot;,
             &amp;quot;elm/html&amp;quot;: &amp;quot;1.0.0&amp;quot;,
             &amp;quot;elm/json&amp;quot;: &amp;quot;1.1.3&amp;quot;,
&lt;span class="gi"&gt;+            &amp;quot;elm/random&amp;quot;: &amp;quot;1.0.0&amp;quot;,&lt;/span&gt;
             &amp;quot;elm/svg&amp;quot;: &amp;quot;1.0.1&amp;quot;
         },
         &amp;quot;indirect&amp;quot;: {
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/f7dba5a3b78980d3c6cc44e17058519d35cee2d8"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This works like a charm, we do have random directions in the console output:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Random directions in the console" src="//mathieu.agopian.info/blog/images/elm-pong_random_directions_console.png"&gt;&lt;/p&gt;
&lt;p&gt;The code is pretty straightforward and easy to understand, but wait a minute:
when will we use this random number? I mean, it would be a pity if the sleep
was expired already, and the game restarted, before we get the new direction.&lt;/p&gt;
&lt;p&gt;The easy way to "synchronize" this is to first query the new random direction,
and then when dealing with the reception of that random number, start the
sleep. This way we're sure it's done in the right order. It does mean we'll
have two messages that are tied together, linked, which feels a bit artifical,
awkward, even though it's doable and wouldn't be that bad.&lt;/p&gt;
&lt;p&gt;Still, let's try the other solution, just to see what it looks like. We'll use
&lt;a href="https://package.elm-lang.org/packages/elm/random/latest/Random#step"&gt;&lt;code&gt;Random.step&lt;/code&gt;&lt;/a&gt;
which needs a &lt;code&gt;Seed&lt;/code&gt;. But the only way we can create a &lt;code&gt;Seed&lt;/code&gt; is with
&lt;a href="https://package.elm-lang.org/packages/elm/random/latest/Random#initialSeed"&gt;&lt;code&gt;Random.initialSeed&lt;/code&gt;&lt;/a&gt;
which takes... a number.&lt;/p&gt;
&lt;p&gt;Ok, but where does this number come from? Let's deal with that later, and use a
perfectly fine number for now: &lt;code&gt;42&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -65,7 +65,6 @@ type Msg&lt;/span&gt;
     | KeyUp PlayerAction
     | RestartGame
     | NewWinner Player
&lt;span class="gd"&gt;-    | NewDirection Int&lt;/span&gt;


 type PlayerAction
&lt;span class="gu"&gt;@@ -165,21 +164,16 @@ update msg model =&lt;/span&gt;
                 sleepCmd =
                     Process.sleep 500
                         |&amp;gt; Task.perform alwaysRestartGame
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                ( randomDirection, _ ) =&lt;/span&gt;
&lt;span class="gi"&gt;+                    Random.initialSeed 42&lt;/span&gt;
&lt;span class="gi"&gt;+                        |&amp;gt; Random.step (Random.int 0 100)&lt;/span&gt;
&lt;span class="gi"&gt;+                        |&amp;gt; Debug.log &amp;quot;Random direction: &amp;quot;&lt;/span&gt;
             in
             ( { model | gameStatus = Winner player, score = updatedScore }
&lt;span class="gd"&gt;-            , Cmd.batch&lt;/span&gt;
&lt;span class="gd"&gt;-                [ sleepCmd&lt;/span&gt;
&lt;span class="gd"&gt;-                , Random.generate NewDirection (Random.int 0 100)&lt;/span&gt;
&lt;span class="gd"&gt;-                ]&lt;/span&gt;
&lt;span class="gi"&gt;+            , sleepCmd&lt;/span&gt;
             )

&lt;span class="gd"&gt;-        NewDirection direction -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            let&lt;/span&gt;
&lt;span class="gd"&gt;-                _ =&lt;/span&gt;
&lt;span class="gd"&gt;-                    Debug.log &amp;quot;New random direction&amp;quot; direction&lt;/span&gt;
&lt;span class="gd"&gt;-            in&lt;/span&gt;
&lt;span class="gd"&gt;-            ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
         KeyDown playerAction -&amp;gt;
             case playerAction of
                 RightPaddleUp -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/5d3630d16f52a01846202cf65baf6771c31570ec"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And we now have the following console logs:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Random directions which are all the same" src="//mathieu.agopian.info/blog/images/elm-pong_random_not_random.png"&gt;&lt;/p&gt;
&lt;p&gt;So far, we don't have any randomness in the numbers we're getting... but that's
expected, because we're always using the same exact &lt;code&gt;Seed&lt;/code&gt; based on the same
exact number: &lt;code&gt;42&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The cool thing with &lt;code&gt;Random.step&lt;/code&gt; is that it gives you a new seed together with
the random number you asked. So if we could store this new seed in the model
and use that on the next call, we'd have a series of different random number:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -18,6 +18,7 @@ type alias Model =&lt;/span&gt;
     , leftPaddleMovement : PaddleMovement
     , gameStatus : GameStatus
     , score : Score
&lt;span class="gi"&gt;+    , seed : Random.Seed&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -96,6 +97,7 @@ init _ =&lt;/span&gt;
             { rightPlayerScore = 0
             , leftPlayerScore = 0
             }
&lt;span class="gi"&gt;+      , seed = Random.initialSeed 42&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -165,12 +167,16 @@ update msg model =&lt;/span&gt;
                     Process.sleep 500
                         |&amp;gt; Task.perform alwaysRestartGame

&lt;span class="gd"&gt;-                ( randomDirection, _ ) =&lt;/span&gt;
&lt;span class="gd"&gt;-                    Random.initialSeed 42&lt;/span&gt;
&lt;span class="gi"&gt;+                ( randomDirection, newSeed ) =&lt;/span&gt;
&lt;span class="gi"&gt;+                    model.seed&lt;/span&gt;
                         |&amp;gt; Random.step (Random.int 0 100)
                         |&amp;gt; Debug.log &amp;quot;Random direction: &amp;quot;
             in
&lt;span class="gd"&gt;-            ( { model | gameStatus = Winner player, score = updatedScore }&lt;/span&gt;
&lt;span class="gi"&gt;+            ( { model&lt;/span&gt;
&lt;span class="gi"&gt;+                | gameStatus = Winner player&lt;/span&gt;
&lt;span class="gi"&gt;+                , score = updatedScore&lt;/span&gt;
&lt;span class="gi"&gt;+                , seed = newSeed&lt;/span&gt;
&lt;span class="gi"&gt;+              }&lt;/span&gt;
             , sleepCmd
             )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/e2ef1bf8d5356371735546fafdf42e88d7781aaa"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Random directions in the console from a seed" src="//mathieu.agopian.info/blog/images/elm-pong_random_directions_from_seed.png"&gt;&lt;/p&gt;
&lt;p&gt;"But Mathieu, that's not really random, we're always using the same seed to
initialize the generator, so we'll always have the exact same number sequence
whenever we reload the page!". That's true. We're using a fixed seed whenever
we start the game so this means players could theoretically remembers the
sequence.&lt;/p&gt;
&lt;p&gt;This could be fixed using several techniques:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;Random.generate&lt;/code&gt; the first time to initialize the seed when the game start&lt;/li&gt;
&lt;li&gt;Use the timestamp of the current time when the program starts&lt;/li&gt;
&lt;li&gt;Use a randomly generated number that was passed to the elm program&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first two solutions are basically the same: in the &lt;code&gt;init&lt;/code&gt; send a command to
get a random number or the current timestamp, store that in the model as the
&lt;code&gt;seed&lt;/code&gt;, and use that from then on. They also both have kind of the same problem
as we saw previously: there's some synchronisation issue. What happens if the
first game restart happens before we got the random number or timestamp back
from the elm runtime? This is obviously very unlikely, especially in our use
case. And we could also decide that it's not a big deal, and use that.&lt;/p&gt;
&lt;p&gt;But as we're going through a series of blog posts that are dedicated to
learning elm, let's take this opportunity to talk about the third solution:
it's based on the &lt;a href="https://guide.elm-lang.org/interop/"&gt;javascript
interop&lt;/a&gt;, and specifically the
&lt;code&gt;flags&lt;/code&gt; in our case.&lt;/p&gt;
&lt;h2&gt;Using flags&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://guide.elm-lang.org/interop/flags.html"&gt;Flags&lt;/a&gt; are a way to pass
initial data from javascript to the elm program. This would be a perfect tool
for our use case: Call &lt;code&gt;Math.random()&lt;/code&gt; in javascript, then pass that initial
seed to the elm program on startup. And from then on use this seed to get new
random numbers (and new seeds, and so on).&lt;/p&gt;
&lt;p&gt;But before we can use flags, we need a bit of setup. Up til now we used an
automatically generated &lt;code&gt;index.html&lt;/code&gt; file containing the inline javascript code
compiled from our elm code. But this means we can't modify the initialization
code as it would be overwritten each and every time we could compile again.&lt;/p&gt;
&lt;p&gt;So we need to generate an &lt;code&gt;index.html&lt;/code&gt; file that imports the generated
javascript, and a piece of javascript code that initializes the elm program.&lt;/p&gt;
&lt;p&gt;Let's base our new &lt;code&gt;index.html&lt;/code&gt; file on the example from the interop
documentation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE HTML&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;UTF-8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Elm pong&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;app.js&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;elm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;elm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We also need to change the &lt;code&gt;scripts&lt;/code&gt; entries in the &lt;code&gt;package.json&lt;/code&gt; file.
You'll see all of that in the
&lt;a href="https://github.com/magopian/elm-pong/commit/7b5a3e88b7d0b42d8670297ac6e59134a3402753"&gt;commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can now change the initialization script so it passes flags to elm.&lt;/p&gt;
&lt;p&gt;Tiny step: let's first pass the number &lt;code&gt;42&lt;/code&gt; from the javascript side:&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;index.html&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;   &amp;lt;div id=&amp;quot;elm&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
   &amp;lt;script&amp;gt;
   var app = Elm.Main.init({
&lt;span class="gd"&gt;-    node: document.getElementById(&amp;#39;elm&amp;#39;)&lt;/span&gt;
&lt;span class="gi"&gt;+    node: document.getElementById(&amp;#39;elm&amp;#39;),&lt;/span&gt;
&lt;span class="gi"&gt;+    flags: 42&lt;/span&gt;
   });
   &amp;lt;/script&amp;gt;
 &amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the &lt;code&gt;src/Main.elm&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -82,11 +82,11 @@ type alias Score =&lt;/span&gt;


 type alias Flags =
&lt;span class="gd"&gt;-    ()&lt;/span&gt;
&lt;span class="gi"&gt;+    Int&lt;/span&gt;


 init : Flags -&amp;gt; ( Model, Cmd Msg )
&lt;span class="gd"&gt;-init _ =&lt;/span&gt;
&lt;span class="gi"&gt;+init seed =&lt;/span&gt;
     ( { ball = initBall
       , rightPaddle = RightPaddle &amp;lt;| initPaddle 480
       , leftPaddle = LeftPaddle &amp;lt;| initPaddle 10
&lt;span class="gu"&gt;@@ -97,7 +97,7 @@ init _ =&lt;/span&gt;
             { rightPlayerScore = 0
             , leftPlayerScore = 0
             }
&lt;span class="gd"&gt;-      , seed = Random.initialSeed 42&lt;/span&gt;
&lt;span class="gi"&gt;+      , seed = Random.initialSeed seed&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/3677ba73201269c673eb3cec57381561e73f68ec"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What that says is we now care very much about the &lt;code&gt;Flags&lt;/code&gt; passed to the &lt;code&gt;init&lt;/code&gt;
function, as we're using it to initialize our seed.
So the &lt;code&gt;Flags&lt;/code&gt; type isn't the &lt;code&gt;unit&lt;/code&gt; anymore (remember, the type that we
usually use in place of values we don't care about), but an &lt;code&gt;Int&lt;/code&gt; to hold our
seed.  It could be a &lt;code&gt;Record&lt;/code&gt;, a &lt;code&gt;String&lt;/code&gt;, a &lt;code&gt;Bool&lt;/code&gt;, a JSON value or any other
of the base types in elm.&lt;/p&gt;
&lt;p&gt;Next tiny step: generate a proper random number on the javascript side, and
pass it to our elm program instead of the number &lt;code&gt;42&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;index.html&lt;/code&gt; file should be modified this way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; &amp;lt;body&amp;gt;
   &amp;lt;div id=&amp;quot;elm&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
   &amp;lt;script&amp;gt;
&lt;span class="gi"&gt;+  let seed = Math.random() * 100; // A random number between 0 and 100.&lt;/span&gt;
   var app = Elm.Main.init({
     node: document.getElementById(&amp;#39;elm&amp;#39;),
&lt;span class="gd"&gt;-    flags: 42&lt;/span&gt;
&lt;span class="gi"&gt;+    flags: Math.round(seed)&lt;/span&gt;
   });
   &amp;lt;/script&amp;gt;
 &amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/7daa5b2a0d74c78e104698f8e0ee7768e7d87616"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We need to &lt;code&gt;Math.round()&lt;/code&gt; the result as otherwise it would be a float and not
an integer.&lt;/p&gt;
&lt;p&gt;And now, every time we refresh the page, we can see that the sequence of random
numbers is different! We did it!&lt;/p&gt;
&lt;p&gt;By the way if we write elm in the first place, it's to write as little javascript as possible. So
maybe we should change the javascript to simply pass the raw random number
which is a float between 0 and 1, and deal with the rest on the elm side?&lt;/p&gt;
&lt;p&gt;Simplify the &lt;code&gt;index.html&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; &amp;lt;body&amp;gt;
   &amp;lt;div id=&amp;quot;elm&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
   &amp;lt;script&amp;gt;
&lt;span class="gd"&gt;-  let seed = Math.random() * 100; // A random number between 0 and 100.&lt;/span&gt;
&lt;span class="gi"&gt;+  let seed = Math.random();&lt;/span&gt;
   var app = Elm.Main.init({
     node: document.getElementById(&amp;#39;elm&amp;#39;),
&lt;span class="gd"&gt;-    flags: Math.round(seed)&lt;/span&gt;
&lt;span class="gi"&gt;+    flags: seed&lt;/span&gt;
   });
   &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the &lt;code&gt;src/Main.elm&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type alias Flags =
&lt;span class="gd"&gt;-    Int&lt;/span&gt;
&lt;span class="gi"&gt;+    Float&lt;/span&gt;


 init : Flags -&amp;gt; ( Model, Cmd Msg )
&lt;span class="gu"&gt;@@ -97,7 +97,13 @@ init seed =&lt;/span&gt;
             { rightPlayerScore = 0
             , leftPlayerScore = 0
             }
&lt;span class="gd"&gt;-      , seed = Random.initialSeed seed&lt;/span&gt;
&lt;span class="gi"&gt;+      , seed =&lt;/span&gt;
&lt;span class="gi"&gt;+            -- A number between 0 and 100&lt;/span&gt;
&lt;span class="gi"&gt;+            seed&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; (*) 100&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; round&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; Random.initialSeed&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/6abb97e68ae1456a7dc1726cc06ce8e2052d1648"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Keep in mind that here we convert the float between 0 to 1 to an integer
between 0 to 100. We could do whatever we want here, for example convert it to
be between 0 and 10000 to have more seeds. Or we could ditch &lt;code&gt;Math.random()&lt;/code&gt;
entirely and use the current timestamp.&lt;/p&gt;
&lt;p&gt;So anyway, now that we have our seed, what kind of random numbers are we going
to generate, and for what usage? Well, in the &lt;code&gt;updateBall&lt;/code&gt; helper we decided
that the vertical speed was going to be between -10 and +10. So let's
initialize our ball &lt;code&gt;vertSpeed&lt;/code&gt; this way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gi"&gt;+randomVertSpeed : Random.Seed -&amp;gt; ( Int, Random.Seed )&lt;/span&gt;
&lt;span class="gi"&gt;+randomVertSpeed seed =&lt;/span&gt;
&lt;span class="gi"&gt;+    Random.step (Random.int -10 10) seed&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 init : Flags -&amp;gt; ( Model, Cmd Msg )
 init seed =
&lt;span class="gd"&gt;-    ( { ball = initBall&lt;/span&gt;
&lt;span class="gi"&gt;+    let&lt;/span&gt;
&lt;span class="gi"&gt;+        initialSeed =&lt;/span&gt;
&lt;span class="gi"&gt;+            -- A number between 0 and 100&lt;/span&gt;
&lt;span class="gi"&gt;+            seed&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; (*) 100&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; round&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; Random.initialSeed&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        ( initialVertSpeed, newSeed ) =&lt;/span&gt;
&lt;span class="gi"&gt;+            randomVertSpeed initialSeed&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        initialBall =&lt;/span&gt;
&lt;span class="gi"&gt;+            { initBall | vertSpeed = initialVertSpeed }&lt;/span&gt;
&lt;span class="gi"&gt;+    in&lt;/span&gt;
&lt;span class="gi"&gt;+    ( { ball = initialBall&lt;/span&gt;
       , rightPaddle = RightPaddle &amp;lt;| initPaddle 480
       , leftPaddle = LeftPaddle &amp;lt;| initPaddle 10
       , rightPaddleMovement = NotMoving
&lt;span class="gu"&gt;@@ -97,12 +116,7 @@ init seed =&lt;/span&gt;
             { rightPlayerScore = 0
             , leftPlayerScore = 0
             }
&lt;span class="gd"&gt;-      , seed =&lt;/span&gt;
&lt;span class="gd"&gt;-            -- A number between -100 and 100&lt;/span&gt;
&lt;span class="gd"&gt;-            seed&lt;/span&gt;
&lt;span class="gd"&gt;-                |&amp;gt; (*) 100&lt;/span&gt;
&lt;span class="gd"&gt;-                |&amp;gt; round&lt;/span&gt;
&lt;span class="gd"&gt;-                |&amp;gt; Random.initialSeed&lt;/span&gt;
&lt;span class="gi"&gt;+      , seed = newSeed&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/24d6be334b72f898cd590cd1250c38d34e5974e8"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So we created a small &lt;code&gt;randomVertSpeed&lt;/code&gt; helper, and using the initial seed we
compute from the javascript value, we set the initial ball's &lt;code&gt;vertSpeed&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And now we want to also use a random vertical seed on each game restart:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -185,16 +185,10 @@ update msg model =&lt;/span&gt;
                 sleepCmd =
                     Process.sleep 500
                         |&amp;gt; Task.perform alwaysRestartGame
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-                ( randomDirection, newSeed ) =&lt;/span&gt;
&lt;span class="gd"&gt;-                    model.seed&lt;/span&gt;
&lt;span class="gd"&gt;-                        |&amp;gt; Random.step (Random.int 0 100)&lt;/span&gt;
&lt;span class="gd"&gt;-                        |&amp;gt; Debug.log &amp;quot;Random direction: &amp;quot;&lt;/span&gt;
             in
             ( { model
                 | gameStatus = Winner player
                 , score = updatedScore
&lt;span class="gd"&gt;-                , seed = newSeed&lt;/span&gt;
               }
             , sleepCmd
             )
&lt;span class="gu"&gt;@@ -244,9 +238,17 @@ update msg model =&lt;/span&gt;
                     )

         RestartGame -&amp;gt;
&lt;span class="gi"&gt;+            let&lt;/span&gt;
&lt;span class="gi"&gt;+                ( vertSpeed, newSeed ) =&lt;/span&gt;
&lt;span class="gi"&gt;+                    randomVertSpeed model.seed&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                ball =&lt;/span&gt;
&lt;span class="gi"&gt;+                    { initBall | vertSpeed = vertSpeed }&lt;/span&gt;
&lt;span class="gi"&gt;+            in&lt;/span&gt;
             ( { model
&lt;span class="gd"&gt;-                | ball = initBall&lt;/span&gt;
&lt;span class="gi"&gt;+                | ball = ball&lt;/span&gt;
                 , gameStatus = NoWinner
&lt;span class="gi"&gt;+                , seed = newSeed&lt;/span&gt;
               }
             , Cmd.none
             )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/2c647468180e3eddb63799493a66c37d2f4e8aa4"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We moved the new vertical speed generation and the seed updating code to the
&lt;code&gt;RestartGame&lt;/code&gt; were we also initialized the ball.&lt;/p&gt;
&lt;p&gt;This is coming alone just perfectly!&lt;/p&gt;
&lt;p&gt;Maybe a last addition: let's shoot the ball towards the last loser&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                 ( vertSpeed, newSeed ) =
                     randomVertSpeed model.seed

&lt;span class="gi"&gt;+                horizSpeedDirection =&lt;/span&gt;
&lt;span class="gi"&gt;+                    -- This number is either 1 if the loser is the right&lt;/span&gt;
&lt;span class="gi"&gt;+                    -- player, or -1 if it&amp;#39;s the left player who lost last.&lt;/span&gt;
&lt;span class="gi"&gt;+                    case model.gameStatus of&lt;/span&gt;
&lt;span class="gi"&gt;+                        Winner RightPlayer -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                            -1&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                        _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                            -- Here we are returning 1 if it&amp;#39;s the LeftPlayer,&lt;/span&gt;
&lt;span class="gi"&gt;+                            -- and at the same time dealing with the `NoWinner`&lt;/span&gt;
&lt;span class="gi"&gt;+                            -- case which shouldn&amp;#39;t happen.&lt;/span&gt;
&lt;span class="gi"&gt;+                            1&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
                 ball =
&lt;span class="gd"&gt;-                    { initBall | vertSpeed = vertSpeed }&lt;/span&gt;
&lt;span class="gi"&gt;+                    { initBall&lt;/span&gt;
&lt;span class="gi"&gt;+                        | vertSpeed = vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                        , horizSpeed = initBall.horizSpeed * horizSpeedDirection&lt;/span&gt;
&lt;span class="gi"&gt;+                    }&lt;/span&gt;
             in
             ( { model
                 | ball = ball
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/542ef76c1664bb8039482179ba25fc125a25eb92"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/14-completed"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And we have now completed our game, and this series of blog posts!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Completed game" src="//mathieu.agopian.info/blog/images/elm-pong_completed.gif"&gt;&lt;/p&gt;
&lt;p&gt;If you've followed along during those four episodes, thanks for your patience!
I hope that those have been useful to you, and feel free to reach out via
&lt;a href="mailto:mathieu@agopian.info?subject=making a pong game in elm"&gt;email&lt;/a&gt;,
&lt;a href="https://twitter.com/magopian"&gt;twitter&lt;/a&gt; or on the
&lt;a href="https://elmlang.herokuapp.com/"&gt;elm slack&lt;/a&gt;.&lt;/p&gt;</content><category term="elm"></category><category term="gamedev"></category></entry><entry><title>Making a pong game in elm (3)</title><link href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-3.html" rel="alternate"></link><published>2019-08-06T14:03:00+02:00</published><updated>2019-08-06T14:03:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2019-08-06:/blog/making-a-pong-game-in-elm-3.html</id><summary type="html">&lt;p&gt;Following the &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm.html"&gt;two&lt;/a&gt;
&lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-2.html"&gt;previous&lt;/a&gt; blog posts, let's
continue taking tiny steps in our endeavour to create a pong game in elm.&lt;/p&gt;
&lt;p&gt;We left off with a ball bouncing off two paddles, and two players able to move
their paddles. And the realization that we were a long way off to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Following the &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm.html"&gt;two&lt;/a&gt;
&lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-2.html"&gt;previous&lt;/a&gt; blog posts, let's
continue taking tiny steps in our endeavour to create a pong game in elm.&lt;/p&gt;
&lt;p&gt;We left off with a ball bouncing off two paddles, and two players able to move
their paddles. And the realization that we were a long way off to have a game
that is at least mildly enjoyable.&lt;/p&gt;
&lt;h2&gt;Moving paddles at the same time&lt;/h2&gt;
&lt;p&gt;For starters, only one player at a time could move their paddle. And this is
because we only moved the paddle when we would detect a &lt;code&gt;onKeyDown&lt;/code&gt;. Which
meant that we depended on a continuous stream of those events to continuously
move the paddle when a player would keep pressing the key.&lt;/p&gt;
&lt;p&gt;But we saw in the previous post that when a player would press a key and hold
it, the events for this key would stop as soon as another key is pressed (eg if
the second player wanted to move their paddle).&lt;/p&gt;
&lt;p&gt;After some &lt;a href="https://stackoverflow.com/questions/5203407/how-to-detect-if-multiple-keys-are-pressed-at-once-using-javascript"&gt;digging around on the internet&lt;/a&gt;
it seems that the proper way to deal with that issue is to keep track of which
key has been pressed (by tracking the &lt;code&gt;onKeyDown&lt;/code&gt; events), and then update the
state of this key when an &lt;code&gt;onKeyUp&lt;/code&gt; is received.&lt;/p&gt;
&lt;p&gt;One way to do that would be to store the pressed keys in a list, and then
remove a key from the list once we detect that it's released.
Another way would be track the key states in a dictionnary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;KeyState&lt;/span&gt;
    &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Pressed&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;NotPressed&lt;/span&gt;

&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kr"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;keyStates&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;arrowUp&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;KeyState&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;arrowDown&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;KeyState&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;charE&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;KeyState&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;charD&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;KeyState&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Thinking about that a bit more: what if we have the following &lt;code&gt;keyState&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;arrowUp&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Pressed&lt;/span&gt;
    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;arrowDown&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Pressed&lt;/span&gt;
    &lt;span class="nf"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This would mean that we have two keys pressed for the same paddle, how would we
decide what to do with that? Maybe we could have some kind of clever algorithm
that would update that dictionnary...&lt;/p&gt;
&lt;p&gt;If you're like me, you try to stay away from any clever code. As stated by
&lt;a href="http://en.wikipedia.org/wiki/Brian_Kernighan"&gt;Brian Kernighan&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Everyone knows that debugging is twice as hard as writing a program in the
first place. So if you're as clever as you can be when you write it, how will
you ever debug it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;— The Elements of Programming Style, 2nd edition, chapter 2&lt;/p&gt;
&lt;p&gt;Maybe we could come up with some other representation of the state. How about
storing the state of the paddles movement?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;PaddleMovement&lt;/span&gt;
    &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MovingUp&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;MovingDown&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;NotMoving&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This way, whenever we get an &lt;code&gt;onKeyDown&lt;/code&gt; for a key, we would update the paddle
movement: pressing down would result in &lt;code&gt;MovingDown&lt;/code&gt;, and then pressing up
(even if we're still pressing down) would update the movement to &lt;code&gt;MovingUp&lt;/code&gt;,
and any &lt;code&gt;onKeyUp&lt;/code&gt; would reset the state to &lt;code&gt;NotMoving&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     { ball : Ball
     , rightPaddle : Paddle
     , leftPaddle : Paddle
&lt;span class="gi"&gt;+    , rightPaddleMovement : PaddleMovement&lt;/span&gt;
&lt;span class="gi"&gt;+    , leftPaddleMovement : PaddleMovement&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -35,6 +37,12 @@ type alias PaddleInfo =&lt;/span&gt;
     }


&lt;span class="gi"&gt;+type PaddleMovement&lt;/span&gt;
&lt;span class="gi"&gt;+    = MovingUp&lt;/span&gt;
&lt;span class="gi"&gt;+    | MovingDown&lt;/span&gt;
&lt;span class="gi"&gt;+    | NotMoving&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 type Msg
     = OnAnimationFrame Float
     | KeyDown PlayerAction
&lt;span class="gu"&gt;@@ -56,6 +64,8 @@ init _ =&lt;/span&gt;
     ( { ball = initBall
       , rightPaddle = RightPaddle &amp;lt;| initPaddle 480
       , leftPaddle = LeftPaddle &amp;lt;| initPaddle 10
&lt;span class="gi"&gt;+      , rightPaddleMovement = NotMoving&lt;/span&gt;
&lt;span class="gi"&gt;+      , leftPaddleMovement = NotMoving&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/f46483f7711aef199228090ecc9b26afe2db2c14"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now we need to update the state in the &lt;code&gt;update&lt;/code&gt; function, in the &lt;code&gt;KeyDown
playerAction&lt;/code&gt; case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         KeyDown playerAction -&amp;gt;
             case playerAction of
                 RightPaddleUp -&amp;gt;
&lt;span class="gd"&gt;-                    ( { model | rightPaddle = model.rightPaddle |&amp;gt; updatePaddle -10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | rightPaddleMovement = MovingUp }&lt;/span&gt;
                     , Cmd.none
                     )

                 RightPaddleDown -&amp;gt;
&lt;span class="gd"&gt;-                    ( { model | rightPaddle = model.rightPaddle |&amp;gt; updatePaddle 10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | rightPaddleMovement = MovingDown }&lt;/span&gt;
                     , Cmd.none
                     )

                 LeftPaddleUp -&amp;gt;
&lt;span class="gd"&gt;-                    ( { model | leftPaddle = model.leftPaddle |&amp;gt; updatePaddle -10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | leftPaddleMovement = MovingUp }&lt;/span&gt;
                     , Cmd.none
                     )

                 LeftPaddleDown -&amp;gt;
&lt;span class="gd"&gt;-                    ( { model | leftPaddle = model.leftPaddle |&amp;gt; updatePaddle 10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | leftPaddleMovement = MovingDown }&lt;/span&gt;
                     , Cmd.none
                     )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/eee13b34387f11dcea7d8bf3b4aeb51f4662335f"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We're updating the paddle movements, or directions... but we aren't actually
moving them. We used to add or substract a number of pixels from their &lt;code&gt;y&lt;/code&gt;
coordinates directly on the &lt;code&gt;KeyDown playerAction&lt;/code&gt; message, but it's not the
case anymore.&lt;/p&gt;
&lt;p&gt;Updating the movements, reacting to player inputs, updating the world and all
that is usually done in a "game loop". The closer we have to a game loop in our
program is the &lt;code&gt;onAnimationFrameDelta&lt;/code&gt; message. So we'll first update our
helper function &lt;code&gt;updatePaddle&lt;/code&gt; to take a &lt;code&gt;PaddleMovement&lt;/code&gt; instead of an amount:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-updatePaddle : Int -&amp;gt; Paddle -&amp;gt; Paddle&lt;/span&gt;
&lt;span class="gd"&gt;-updatePaddle amount paddle =&lt;/span&gt;
&lt;span class="gi"&gt;+updatePaddle : PaddleMovement -&amp;gt; Paddle -&amp;gt; Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+updatePaddle movement paddle =&lt;/span&gt;
&lt;span class="gi"&gt;+    let&lt;/span&gt;
&lt;span class="gi"&gt;+        amount =&lt;/span&gt;
&lt;span class="gi"&gt;+            case movement of&lt;/span&gt;
&lt;span class="gi"&gt;+                MovingUp -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    -10&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                MovingDown -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    10&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                NotMoving -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    0&lt;/span&gt;
&lt;span class="gi"&gt;+    in&lt;/span&gt;
     case paddle of
         RightPaddle paddleInfo -&amp;gt;
             { paddleInfo | y = paddleInfo.y + amount }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And we can now use that in the &lt;code&gt;update&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                         | x = ball.x + horizSpeed
                         , horizSpeed = horizSpeed
                     }
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                updatedRightPaddle =&lt;/span&gt;
&lt;span class="gi"&gt;+                    updatePaddle model.rightPaddleMovement model.rightPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                updatedLeftPaddle =&lt;/span&gt;
&lt;span class="gi"&gt;+                    updatePaddle model.leftPaddleMovement model.leftPaddle&lt;/span&gt;
             in
&lt;span class="gd"&gt;-            ( { model | ball = updatedBall }, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+            ( { model&lt;/span&gt;
&lt;span class="gi"&gt;+                | ball = updatedBall&lt;/span&gt;
&lt;span class="gi"&gt;+                , rightPaddle = updatedRightPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+                , leftPaddle = updatedLeftPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+              }&lt;/span&gt;
&lt;span class="gi"&gt;+            , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+            )&lt;/span&gt;

         KeyDown playerAction -&amp;gt;
             case playerAction of
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/522243fb0aca0faec6b8af6889562c59597d0c0e"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And we're now done, both paddles can move at the same time!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Both paddles moving at the same time" src="//mathieu.agopian.info/blog/images/elm-pong_both_paddles_same_time.gif"&gt;&lt;/p&gt;
&lt;p&gt;What is it that you're saying? That I forgot to manage the case when there's no
player action anymore? Of course I didn't, I just wanted to make sure you were
still following along. And you were, well done. I never doubted you.&lt;/p&gt;
&lt;p&gt;We now need to also subscribe to the &lt;code&gt;onKeyUp&lt;/code&gt; events for the keys we're using
for the player actions, which means adding&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;KeyUp PlayerAction&lt;/code&gt; variant to the &lt;code&gt;Msg&lt;/code&gt; type&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;case&lt;/code&gt; to the &lt;code&gt;update&lt;/code&gt; function to deal with this new message&lt;/li&gt;
&lt;li&gt;a new subscription to the &lt;code&gt;Browser.Events.onKeyUp&lt;/code&gt; events&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type Msg
     = OnAnimationFrame Float
     | KeyDown PlayerAction
&lt;span class="gi"&gt;+    | KeyUp PlayerAction&lt;/span&gt;


 type PlayerAction
&lt;span class="gu"&gt;@@ -160,6 +161,28 @@ update msg model =&lt;/span&gt;
                     , Cmd.none
                     )

&lt;span class="gi"&gt;+        KeyUp playerAction -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            case playerAction of&lt;/span&gt;
&lt;span class="gi"&gt;+                RightPaddleUp -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | rightPaddleMovement = NotMoving }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                RightPaddleDown -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | rightPaddleMovement = NotMoving }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                LeftPaddleUp -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | leftPaddleMovement = NotMoving }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                LeftPaddleDown -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | leftPaddleMovement = NotMoving }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;

 updatePaddle : PaddleMovement -&amp;gt; Paddle -&amp;gt; Paddle
 updatePaddle movement paddle =
&lt;span class="gu"&gt;@@ -248,6 +271,7 @@ subscriptions _ =&lt;/span&gt;
     Sub.batch
         [ Browser.Events.onAnimationFrameDelta OnAnimationFrame
         , Browser.Events.onKeyDown (Decode.map KeyDown keyDecoder)
&lt;span class="gi"&gt;+        , Browser.Events.onKeyUp (Decode.map KeyUp keyDecoder)&lt;/span&gt;
         ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/b894e75dffa09215f822fb24d5dc63c2cfd2885d"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/8-move-paddles-same-time"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Clamping the paddles&lt;/h2&gt;
&lt;p&gt;While it may be fun at first to be able to move your paddle off the screen, and
while it may be seen as an extra challenge, let's stick to the original
concept, and prevent the paddles from disappearing.&lt;/p&gt;
&lt;p&gt;We can do that by making sure we don't update the paddle's &lt;code&gt;y&lt;/code&gt; position with a
value that's "out of bounds":&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     in
     case paddle of
         RightPaddle paddleInfo -&amp;gt;
&lt;span class="gd"&gt;-            { paddleInfo | y = paddleInfo.y + amount }&lt;/span&gt;
&lt;span class="gi"&gt;+            { paddleInfo&lt;/span&gt;
&lt;span class="gi"&gt;+                | y =&lt;/span&gt;
&lt;span class="gi"&gt;+                    paddleInfo.y&lt;/span&gt;
&lt;span class="gi"&gt;+                        + amount&lt;/span&gt;
&lt;span class="gi"&gt;+                        |&amp;gt; clamp 0 (500 - paddleInfo.height)&lt;/span&gt;
&lt;span class="gi"&gt;+            }&lt;/span&gt;
                 |&amp;gt; RightPaddle

         LeftPaddle paddleInfo -&amp;gt;
&lt;span class="gd"&gt;-            { paddleInfo | y = paddleInfo.y + amount }&lt;/span&gt;
&lt;span class="gi"&gt;+            { paddleInfo&lt;/span&gt;
&lt;span class="gi"&gt;+                | y =&lt;/span&gt;
&lt;span class="gi"&gt;+                    paddleInfo.y&lt;/span&gt;
&lt;span class="gi"&gt;+                        + amount&lt;/span&gt;
&lt;span class="gi"&gt;+                        |&amp;gt; clamp 0 (500 - paddleInfo.height)&lt;/span&gt;
&lt;span class="gi"&gt;+            }&lt;/span&gt;
                 |&amp;gt; LeftPaddle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/ee829a9d773d6f7f6dd0c6dafafb74489e890af4"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/9-clamp-paddles"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here we're using the very convenient
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Basics#clamp"&gt;clamp&lt;/a&gt;
helper to make sure the &lt;code&gt;y&lt;/code&gt; coordinates of the paddles can't go above &lt;code&gt;500 -
paddle.height&lt;/code&gt; (which means the full paddle is always displayed), nor below 0.&lt;/p&gt;
&lt;h2&gt;Adding some verticalization&lt;/h2&gt;
&lt;p&gt;Up till now the ball would always move horizontally. Never up or down. And as
such, the game... well, let's say that it wasn't very challenging. But that
changes now!&lt;/p&gt;
&lt;p&gt;Let's add a &lt;code&gt;vertSpeed&lt;/code&gt; to the &lt;code&gt;ball&lt;/code&gt;, and set it to a fixed value for now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -21,6 +21,7 @@ type alias Ball =&lt;/span&gt;
     , y : Int
     , radius : Int
     , horizSpeed : Int
&lt;span class="gi"&gt;+    , vertSpeed : Int&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -78,6 +79,7 @@ initBall =&lt;/span&gt;
     , y = 250
     , radius = 10
     , horizSpeed = 4
&lt;span class="gi"&gt;+    , vertSpeed = 2&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -122,6 +124,7 @@ update msg model =&lt;/span&gt;
                 updatedBall =
                     { ball
                         | x = ball.x + horizSpeed
&lt;span class="gi"&gt;+                        , y = ball.y + ball.vertSpeed&lt;/span&gt;
                         , horizSpeed = horizSpeed
                     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/1098a7ddfc8f815b486bb1f4bf6bd0ae3c8bfd94"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We're not doing anything fancy here: adding a new field to the &lt;code&gt;Ball&lt;/code&gt; record,
initializing it to &lt;code&gt;2&lt;/code&gt;, and adding it to the &lt;code&gt;y&lt;/code&gt; coordinates of the ball on
each frame.&lt;/p&gt;
&lt;p&gt;And behold the result!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ball moving with a vertical speed" src="//mathieu.agopian.info/blog/images/elm-pong_vertical_ball.gif"&gt;&lt;/p&gt;
&lt;p&gt;And now we know what we need to do next:&lt;/p&gt;
&lt;h2&gt;Bouncing the ball off the walls&lt;/h2&gt;
&lt;p&gt;What good is a ball that we can't see anymore? Let's fix that by mimicking what
we did for the bouncing off the paddles:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                     else
                         ball.horizSpeed

&lt;span class="gi"&gt;+                shouldBounceVertically =&lt;/span&gt;
&lt;span class="gi"&gt;+                    shouldBallBounceVertically model.ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                vertSpeed =&lt;/span&gt;
&lt;span class="gi"&gt;+                    if shouldBounceVertically then&lt;/span&gt;
&lt;span class="gi"&gt;+                        ball.vertSpeed * -1&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                    else&lt;/span&gt;
&lt;span class="gi"&gt;+                        ball.vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
                 updatedBall =
                     { ball
                         | x = ball.x + horizSpeed
&lt;span class="gd"&gt;-                        , y = ball.y + ball.vertSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                        , y = ball.y + vertSpeed&lt;/span&gt;
                         , horizSpeed = horizSpeed
&lt;span class="gi"&gt;+                        , vertSpeed = vertSpeed&lt;/span&gt;
                     }

                 updatedRightPaddle =
&lt;span class="gu"&gt;@@ -233,6 +244,15 @@ shouldBallBounce paddle ball =&lt;/span&gt;
                 &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)


&lt;span class="gi"&gt;+shouldBallBounceVertically : Ball -&amp;gt; Bool&lt;/span&gt;
&lt;span class="gi"&gt;+shouldBallBounceVertically ball =&lt;/span&gt;
&lt;span class="gi"&gt;+    let&lt;/span&gt;
&lt;span class="gi"&gt;+        radius =&lt;/span&gt;
&lt;span class="gi"&gt;+            ball.radius&lt;/span&gt;
&lt;span class="gi"&gt;+    in&lt;/span&gt;
&lt;span class="gi"&gt;+    ball.y &amp;lt;= radius || ball.y &amp;gt;= (500 - radius)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 view : Model -&amp;gt; Svg.Svg Msg
 view { ball, rightPaddle, leftPaddle } =
     svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/09af771d95ca043f6ba24cc60d36e93d0febf046"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Losing and winning&lt;/h2&gt;
&lt;p&gt;Whenever the ball reaches the left or right side of the screen, the game should
reset, and the opposite player should win a point.&lt;/p&gt;
&lt;p&gt;So let's detect the win/lose condition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -44,6 +44,11 @@ type PaddleMovement&lt;/span&gt;
     | NotMoving


&lt;span class="gi"&gt;+type Player&lt;/span&gt;
&lt;span class="gi"&gt;+    = LeftPlayer&lt;/span&gt;
&lt;span class="gi"&gt;+    | RightPlayer&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 type Msg
     = OnAnimationFrame Float
     | KeyDown PlayerAction
&lt;span class="gu"&gt;@@ -144,6 +149,10 @@ update msg model =&lt;/span&gt;

                 updatedLeftPaddle =
                     updatePaddle model.leftPaddleMovement model.leftPaddle
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                winner =&lt;/span&gt;
&lt;span class="gi"&gt;+                    maybeWinner updatedBall&lt;/span&gt;
&lt;span class="gi"&gt;+                        |&amp;gt; Debug.log &amp;quot;Winner&amp;quot;&lt;/span&gt;
             in
             ( { model
                 | ball = updatedBall
&lt;span class="gu"&gt;@@ -253,6 +262,18 @@ shouldBallBounceVertically ball =&lt;/span&gt;
     ball.y &amp;lt;= radius || ball.y &amp;gt;= (500 - radius)


&lt;span class="gi"&gt;+maybeWinner : Ball -&amp;gt; Maybe Player&lt;/span&gt;
&lt;span class="gi"&gt;+maybeWinner ball =&lt;/span&gt;
&lt;span class="gi"&gt;+    if ball.x &amp;lt;= ball.radius then&lt;/span&gt;
&lt;span class="gi"&gt;+        Just RightPlayer&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+    else if ball.x &amp;gt;= (500 - ball.radius) then&lt;/span&gt;
&lt;span class="gi"&gt;+        Just LeftPlayer&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+    else&lt;/span&gt;
&lt;span class="gi"&gt;+        Nothing&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 view : Model -&amp;gt; Svg.Svg Msg
 view { ball, rightPaddle, leftPaddle } =
     svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/70edf149c9bb77cc59de4126c4e2c7febd8844e3"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This displays &lt;code&gt;Winner: Nothing&lt;/code&gt; in the console on each frame, until there's a
&lt;code&gt;Winner: Just RightPlayer&lt;/code&gt; as soon as the ball hits the right border... and
then on each following frame, as the game doesn't reset. Yet.&lt;/p&gt;
&lt;p&gt;"But Mathieu, what is this &lt;code&gt;Maybe&lt;/code&gt; thing, and all that &lt;code&gt;Just&lt;/code&gt; and &lt;code&gt;Nothing&lt;/code&gt;
nonsense?".
Hoy! Behave, that's no nonsense, that's proper engineering! It's called the
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Maybe"&gt;Maybe type&lt;/a&gt; and
it represents "values that may or may not exist", which is exactly what we need
here: there may be a winner, or maybe not. The result is either "just a player"
or "nothing" (no winner). And we can now use this &lt;code&gt;Maybe Player&lt;/code&gt; to update a
new custom type that we'll call &lt;code&gt;GameStatus&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -13,6 +13,7 @@ type alias Model =&lt;/span&gt;
     , leftPaddle : Paddle
     , rightPaddleMovement : PaddleMovement
     , leftPaddleMovement : PaddleMovement
&lt;span class="gi"&gt;+    , gameStatus : GameStatus&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -49,6 +50,11 @@ type Player&lt;/span&gt;
     | RightPlayer


&lt;span class="gi"&gt;+type GameStatus&lt;/span&gt;
&lt;span class="gi"&gt;+    = NoWinner&lt;/span&gt;
&lt;span class="gi"&gt;+    | Winner Player&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 type Msg
     = OnAnimationFrame Float
     | KeyDown PlayerAction
&lt;span class="gu"&gt;@@ -73,6 +79,7 @@ init _ =&lt;/span&gt;
       , leftPaddle = LeftPaddle &amp;lt;| initPaddle 10
       , rightPaddleMovement = NotMoving
       , leftPaddleMovement = NotMoving
&lt;span class="gi"&gt;+      , gameStatus = NoWinner&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -150,14 +157,19 @@ update msg model =&lt;/span&gt;
                 updatedLeftPaddle =
                     updatePaddle model.leftPaddleMovement model.leftPaddle

&lt;span class="gd"&gt;-                winner =&lt;/span&gt;
&lt;span class="gd"&gt;-                    maybeWinner updatedBall&lt;/span&gt;
&lt;span class="gd"&gt;-                        |&amp;gt; Debug.log &amp;quot;Winner&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+                gameStatus =&lt;/span&gt;
&lt;span class="gi"&gt;+                    case maybeWinner updatedBall of&lt;/span&gt;
&lt;span class="gi"&gt;+                        Nothing -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                            NoWinner&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                        Just player -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                            Winner player&lt;/span&gt;
             in
             ( { model
                 | ball = updatedBall
                 , rightPaddle = updatedRightPaddle
                 , leftPaddle = updatedLeftPaddle
&lt;span class="gi"&gt;+                , gameStatus = gameStatus&lt;/span&gt;
               }
             , Cmd.none
             )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/583616d83b7371cce1fe2e79647b96ea57eea9a1"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We now have a proper &lt;code&gt;GameState&lt;/code&gt; that gets updated whenever the ball reaches
the left or right, but we aren't doing anything with it yet. What should we do
with it?&lt;/p&gt;
&lt;p&gt;Well... if we're in the &lt;code&gt;NoWinner&lt;/code&gt; state, it means we should be playing, and as
such listening to user input and animation frames. If we're in the &lt;code&gt;Winner ...&lt;/code&gt;
state, we shouldn't.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; subscriptions : Model -&amp;gt; Sub Msg
&lt;span class="gd"&gt;-subscriptions _ =&lt;/span&gt;
&lt;span class="gd"&gt;-    Sub.batch&lt;/span&gt;
&lt;span class="gd"&gt;-        [ Browser.Events.onAnimationFrameDelta OnAnimationFrame&lt;/span&gt;
&lt;span class="gd"&gt;-        , Browser.Events.onKeyDown (Decode.map KeyDown keyDecoder)&lt;/span&gt;
&lt;span class="gd"&gt;-        , Browser.Events.onKeyUp (Decode.map KeyUp keyDecoder)&lt;/span&gt;
&lt;span class="gd"&gt;-        ]&lt;/span&gt;
&lt;span class="gi"&gt;+subscriptions model =&lt;/span&gt;
&lt;span class="gi"&gt;+    case model.gameStatus of&lt;/span&gt;
&lt;span class="gi"&gt;+        NoWinner -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            Sub.batch&lt;/span&gt;
&lt;span class="gi"&gt;+                [ Browser.Events.onAnimationFrameDelta OnAnimationFrame&lt;/span&gt;
&lt;span class="gi"&gt;+                , Browser.Events.onKeyDown (Decode.map KeyDown keyDecoder)&lt;/span&gt;
&lt;span class="gi"&gt;+                , Browser.Events.onKeyUp (Decode.map KeyUp keyDecoder)&lt;/span&gt;
&lt;span class="gi"&gt;+                ]&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        Winner _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            Sub.none&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/3f529a05544633f974eb0ab55b3a9978b05d4d8b"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As easy as this! Now the game stops as soon as a player wins.&lt;/p&gt;
&lt;p&gt;Now let's restart the game after a 500 milliseconds delay. For that, we'll
introduce a new concept: the
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Task"&gt;Task&lt;/a&gt; which
makes "it easy to describe asynchronous operations": in our case, the &lt;code&gt;Task&lt;/code&gt;
will be a
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Process#sleep"&gt;Process.sleep&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once we have the &lt;code&gt;Task&lt;/code&gt;, we can ask the elm runtime to execute it for us using
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Task#perform"&gt;Task.perform&lt;/a&gt;
which will return a &lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Platform-Cmd#Cmd"&gt;Cmd
Msg&lt;/a&gt;.
We'll attach a new &lt;code&gt;Msg&lt;/code&gt; variant that we'll call &lt;code&gt;SleepDone&lt;/code&gt; to that &lt;code&gt;Cmd&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; import Browser
 import Browser.Events
 import Json.Decode as Decode
&lt;span class="gi"&gt;+import Process&lt;/span&gt;
 import Svg exposing (..)
 import Svg.Attributes exposing (..)
&lt;span class="gi"&gt;+import Task&lt;/span&gt;


 type alias Model =
&lt;span class="gu"&gt;@@ -59,6 +61,7 @@ type Msg&lt;/span&gt;
     = OnAnimationFrame Float
     | KeyDown PlayerAction
     | KeyUp PlayerAction
&lt;span class="gi"&gt;+    | SleepDone ()&lt;/span&gt;


 type PlayerAction
&lt;span class="gu"&gt;@@ -157,13 +160,18 @@ update msg model =&lt;/span&gt;
                 updatedLeftPaddle =
                     updatePaddle model.leftPaddleMovement model.leftPaddle

&lt;span class="gd"&gt;-                gameStatus =&lt;/span&gt;
&lt;span class="gi"&gt;+                ( gameStatus, cmd ) =&lt;/span&gt;
                     case maybeWinner updatedBall of
                         Nothing -&amp;gt;
&lt;span class="gd"&gt;-                            NoWinner&lt;/span&gt;
&lt;span class="gi"&gt;+                            ( NoWinner, Cmd.none )&lt;/span&gt;

                         Just player -&amp;gt;
&lt;span class="gd"&gt;-                            Winner player&lt;/span&gt;
&lt;span class="gi"&gt;+                            let&lt;/span&gt;
&lt;span class="gi"&gt;+                                delayCmd =&lt;/span&gt;
&lt;span class="gi"&gt;+                                    Process.sleep 500&lt;/span&gt;
&lt;span class="gi"&gt;+                                        |&amp;gt; Task.perform SleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+                            in&lt;/span&gt;
&lt;span class="gi"&gt;+                            ( Winner player, delayCmd )&lt;/span&gt;
             in
             ( { model
                 | ball = updatedBall
&lt;span class="gu"&gt;@@ -171,7 +179,7 @@ update msg model =&lt;/span&gt;
                 , leftPaddle = updatedLeftPaddle
                 , gameStatus = gameStatus
               }
&lt;span class="gd"&gt;-            , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+            , cmd&lt;/span&gt;
             )

         KeyDown playerAction -&amp;gt;
&lt;span class="gu"&gt;@@ -218,6 +226,13 @@ update msg model =&lt;/span&gt;
                     , Cmd.none
                     )

&lt;span class="gi"&gt;+        SleepDone _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            let&lt;/span&gt;
&lt;span class="gi"&gt;+                _ =&lt;/span&gt;
&lt;span class="gi"&gt;+                    Debug.log &amp;quot;restart&amp;quot; &amp;quot;game&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+            in&lt;/span&gt;
&lt;span class="gi"&gt;+            ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;

 updatePaddle : PaddleMovement -&amp;gt; Paddle -&amp;gt; Paddle
 updatePaddle movement paddle =
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/fdcaa7f9437ff5236ea22b108b88898f20f6a995"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This one involves quite a lot, so let's decompose it piece by piece:&lt;/p&gt;
&lt;p&gt;On each frame, we now not only change the game status if needed, we also send a
&lt;code&gt;Cmd&lt;/code&gt; to the elm runtime if there was a win.
This command is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;Process&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
    &lt;span class="nf"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt; &lt;span class="kt"&gt;SleepDone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As a reminder, that's the same as writing&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt; &lt;span class="kt"&gt;SleepDone&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Process&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Task.perform&lt;/code&gt; translates a &lt;code&gt;Task&lt;/code&gt; into a &lt;code&gt;Cmd&lt;/code&gt;, which can then be sent to
the elm runtime, by returning it from the &lt;code&gt;update&lt;/code&gt; function. Which brings us to
the &lt;code&gt;( Model, Cmd Msg )&lt;/code&gt; in the type signature of the &lt;code&gt;update&lt;/code&gt; function, which
is a
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Tuple"&gt;tuple type&lt;/a&gt;. A
&lt;code&gt;tuple&lt;/code&gt; is a fixed size list of things with types which may differ. This is
very different from the
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/List"&gt;List type&lt;/a&gt;
which is a variable size list of things of the same type.&lt;/p&gt;
&lt;p&gt;Back to the code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;gameStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt;
    &lt;span class="kr"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;maybeWinner&lt;/span&gt; &lt;span class="nv"&gt;updatedBall&lt;/span&gt; &lt;span class="kr"&gt;of&lt;/span&gt;
        &lt;span class="kt"&gt;Nothing&lt;/span&gt; &lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;NoWinner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;none&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="nv"&gt;player&lt;/span&gt; &lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kr"&gt;let&lt;/span&gt;
                &lt;span class="nv"&gt;delayCmd&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt;
                    &lt;span class="kt"&gt;Process&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
                        &lt;span class="nf"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Task&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;perform&lt;/span&gt; &lt;span class="kt"&gt;SleepDone&lt;/span&gt;
            &lt;span class="kr"&gt;in&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kt"&gt;Winner&lt;/span&gt; &lt;span class="nv"&gt;player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;delayCmd&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The first part before the &lt;code&gt;=&lt;/code&gt; sign is destructuring a 2-tuple into two
variables names &lt;code&gt;gameStatus&lt;/code&gt; and &lt;code&gt;cmd&lt;/code&gt;. The &lt;code&gt;cmd&lt;/code&gt; is the command that will be
returned by the &lt;code&gt;update&lt;/code&gt; function if we're processing an
&lt;code&gt;onAnimationFrameDelta&lt;/code&gt; message.&lt;/p&gt;
&lt;p&gt;And this command is either &lt;code&gt;Cmd.none&lt;/code&gt; (no command) if there's &lt;code&gt;NoWinner&lt;/code&gt;, or
the &lt;code&gt;delayCmd&lt;/code&gt; if there's a winner.&lt;/p&gt;
&lt;p&gt;The end of the previous diff is simply processing the &lt;code&gt;SleepDone&lt;/code&gt; message. At
the moment the only thing it's doing is printing a debug message in the
console. As we've seen previously, using the &lt;code&gt;_&lt;/code&gt; means we don't care about the
variable (so we don't care about the &lt;code&gt;"game"&lt;/code&gt; string that we passed to the
&lt;code&gt;Debug.log&lt;/code&gt; function, and we don't care either about the data attached to the
&lt;code&gt;SleepDone&lt;/code&gt; message).&lt;/p&gt;
&lt;p&gt;"But wait Mathieu, if we don't care about the data attached to the &lt;code&gt;SleepDone&lt;/code&gt;
variant, why does it even have it in the first place?". Very good question.
Brace yourselves for the answer:&lt;/p&gt;
&lt;p&gt;A task always returns something. Sometimes, this "thing" is uninteresting, in
which case we use the &lt;code&gt;unit&lt;/code&gt;, which is represented by &lt;code&gt;()&lt;/code&gt; and has only one
value: &lt;code&gt;()&lt;/code&gt;. And if we go back to the &lt;code&gt;Process.sleep&lt;/code&gt; signature, it says
it returns a &lt;code&gt;Task x ()&lt;/code&gt; (so a &lt;code&gt;Task&lt;/code&gt; that returns a unit).&lt;/p&gt;
&lt;p&gt;And this thing that the &lt;code&gt;Task&lt;/code&gt; returns is the same thing that is attached to the
message that the &lt;code&gt;Task.perform&lt;/code&gt; takes as its first argument. Hence the
&lt;code&gt;SleepDone ()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let me show you a little nugget of cleverness (but please remember, being
clever is usually a bad idea, so use this sparingly):
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Basics#always"&gt;the &lt;code&gt;always&lt;/code&gt; helper&lt;/a&gt;
is a function that always returns the same thing, whatever the argument you
give it. This seems pretty useless, but we could use it to our advantage in our
case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -61,7 +61,7 @@ type Msg&lt;/span&gt;
     = OnAnimationFrame Float
     | KeyDown PlayerAction
     | KeyUp PlayerAction
&lt;span class="gd"&gt;-    | SleepDone ()&lt;/span&gt;
&lt;span class="gi"&gt;+    | SleepDone&lt;/span&gt;


 type PlayerAction
&lt;span class="gu"&gt;@@ -169,7 +169,7 @@ update msg model =&lt;/span&gt;
                             let
                                 delayCmd =
                                     Process.sleep 500
&lt;span class="gd"&gt;-                                        |&amp;gt; Task.perform SleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+                                        |&amp;gt; Task.perform (always SleepDone)&lt;/span&gt;
                             in
                             ( Winner player, delayCmd )
             in
&lt;span class="gu"&gt;@@ -226,7 +226,7 @@ update msg model =&lt;/span&gt;
                     , Cmd.none
                     )

&lt;span class="gd"&gt;-        SleepDone _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+        SleepDone -&amp;gt;&lt;/span&gt;
             let
                 _ =
                     Debug.log &amp;quot;restart&amp;quot; &amp;quot;game&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We don't care about the data attached to the message by &lt;code&gt;Task.perform&lt;/code&gt;, so we
just discard it by using the &lt;code&gt;always&lt;/code&gt; helper. This might seem confusing, but
keep in mind that a custom type variant is also a constructor. So when we
wanted to create a new right paddle, we would do &lt;code&gt;RightPaddle paddleInfo&lt;/code&gt;.
You can see &lt;code&gt;RightPaddle&lt;/code&gt; as a function with the following type signature:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RightPaddle : PaddleInfo -&amp;gt; Paddle&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;So you can also see &lt;code&gt;(always SleepDone)&lt;/code&gt; as a function that takes a parameter
and returns &lt;code&gt;SleepDone&lt;/code&gt;, and we could call it &lt;code&gt;alwaysSleepDone&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;alwaysSleepDone : a -&amp;gt; Msg&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Using a type starting with a lowercase (the &lt;code&gt;a&lt;/code&gt; in the type signature just
above) means that it could be any type, including a &lt;code&gt;unit&lt;/code&gt;. In any case, we
don't care about the type that's being passed to the helper, so no need to be
specific here.&lt;/p&gt;
&lt;p&gt;So the final diff would be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -61,7 +61,7 @@ type Msg&lt;/span&gt;
     = OnAnimationFrame Float
     | KeyDown PlayerAction
     | KeyUp PlayerAction
&lt;span class="gd"&gt;-    | SleepDone ()&lt;/span&gt;
&lt;span class="gi"&gt;+    | SleepDone&lt;/span&gt;


 type PlayerAction
&lt;span class="gu"&gt;@@ -167,9 +167,13 @@ update msg model =&lt;/span&gt;

                         Just player -&amp;gt;
                             let
&lt;span class="gi"&gt;+                                alwaysSleepDone : a -&amp;gt; Msg&lt;/span&gt;
&lt;span class="gi"&gt;+                                alwaysSleepDone =&lt;/span&gt;
&lt;span class="gi"&gt;+                                    always SleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
                                 delayCmd =
                                     Process.sleep 500
&lt;span class="gd"&gt;-                                        |&amp;gt; Task.perform SleepDone&lt;/span&gt;
&lt;span class="gi"&gt;+                                        |&amp;gt; Task.perform alwaysSleepDone&lt;/span&gt;
                             in
                             ( Winner player, delayCmd )
             in
&lt;span class="gu"&gt;@@ -226,7 +230,7 @@ update msg model =&lt;/span&gt;
                     , Cmd.none
                     )

&lt;span class="gd"&gt;-        SleepDone _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+        SleepDone -&amp;gt;&lt;/span&gt;
             let
                 _ =
                     Debug.log &amp;quot;restart&amp;quot; &amp;quot;game&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/1ab844992f82f875f31fca402070ee626b11d106"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/10-win-lose"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This didn't give us much in terms of readability, or at least it's debatable. I
know I'd rather have obvious types and slightly more complex code, but I guess
that's a matter of taste.&lt;/p&gt;
&lt;h2&gt;Restarting the game&lt;/h2&gt;
&lt;p&gt;So what should happen once the delay has elapsed, and the game should
"restart"? Well the ball should be reset to its initial position and speed, and
the game status should be &lt;code&gt;NoWinner&lt;/code&gt; again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         SleepDone -&amp;gt;
&lt;span class="gd"&gt;-            let&lt;/span&gt;
&lt;span class="gd"&gt;-                _ =&lt;/span&gt;
&lt;span class="gd"&gt;-                    Debug.log &amp;quot;restart&amp;quot; &amp;quot;game&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            in&lt;/span&gt;
&lt;span class="gd"&gt;-            ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+            ( { model&lt;/span&gt;
&lt;span class="gi"&gt;+                | ball = initBall&lt;/span&gt;
&lt;span class="gi"&gt;+                , gameStatus = NoWinner&lt;/span&gt;
&lt;span class="gi"&gt;+              }&lt;/span&gt;
&lt;span class="gi"&gt;+            , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+            )&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/49ca518a40f427c0b6f7fd24c2493522e568fe8f"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once the ball touches one of the "goals", the whole game stops for half a
second, and then the ball position is reset.&lt;/p&gt;
&lt;h2&gt;Keeping track of the score&lt;/h2&gt;
&lt;p&gt;Let's add a &lt;code&gt;score&lt;/code&gt; record with the &lt;code&gt;rightPlayerScore&lt;/code&gt; and &lt;code&gt;leftPlayerScore&lt;/code&gt;
fields to the model, and update them whenever there's a win:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -16,6 +16,7 @@ type alias Model =&lt;/span&gt;
     , rightPaddleMovement : PaddleMovement
     , leftPaddleMovement : PaddleMovement
     , gameStatus : GameStatus
&lt;span class="gi"&gt;+    , score : Score&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -71,6 +72,12 @@ type PlayerAction&lt;/span&gt;
     | LeftPaddleDown


&lt;span class="gi"&gt;+type alias Score =&lt;/span&gt;
&lt;span class="gi"&gt;+    { rightPlayerScore : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    , leftPlayerScore : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 type alias Flags =
     ()

&lt;span class="gu"&gt;@@ -83,6 +90,10 @@ init _ =&lt;/span&gt;
       , rightPaddleMovement = NotMoving
       , leftPaddleMovement = NotMoving
       , gameStatus = NoWinner
&lt;span class="gi"&gt;+      , score =&lt;/span&gt;
&lt;span class="gi"&gt;+            { rightPlayerScore = 0&lt;/span&gt;
&lt;span class="gi"&gt;+            , leftPlayerScore = 0&lt;/span&gt;
&lt;span class="gi"&gt;+            }&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -160,10 +171,10 @@ update msg model =&lt;/span&gt;
                 updatedLeftPaddle =
                     updatePaddle model.leftPaddleMovement model.leftPaddle

&lt;span class="gd"&gt;-                ( gameStatus, cmd ) =&lt;/span&gt;
&lt;span class="gi"&gt;+                ( gameStatus, score, cmd ) =&lt;/span&gt;
                     case maybeWinner updatedBall of
                         Nothing -&amp;gt;
&lt;span class="gd"&gt;-                            ( NoWinner, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+                            ( NoWinner, model.score, Cmd.none )&lt;/span&gt;

                         Just player -&amp;gt;
                             let
&lt;span class="gu"&gt;@@ -174,14 +185,19 @@ update msg model =&lt;/span&gt;
                                 delayCmd =
                                     Process.sleep 500
                                         |&amp;gt; Task.perform alwaysSleepDone
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                                updatedScore =&lt;/span&gt;
&lt;span class="gi"&gt;+                                    updateScores model.score player&lt;/span&gt;
&lt;span class="gi"&gt;+                                        |&amp;gt; Debug.log &amp;quot;score&amp;quot;&lt;/span&gt;
                             in
&lt;span class="gd"&gt;-                            ( Winner player, delayCmd )&lt;/span&gt;
&lt;span class="gi"&gt;+                            ( Winner player, updatedScore, delayCmd )&lt;/span&gt;
             in
             ( { model
                 | ball = updatedBall
                 , rightPaddle = updatedRightPaddle
                 , leftPaddle = updatedLeftPaddle
                 , gameStatus = gameStatus
&lt;span class="gi"&gt;+                , score = score&lt;/span&gt;
               }
             , cmd
             )
&lt;span class="gu"&gt;@@ -306,6 +322,16 @@ maybeWinner ball =&lt;/span&gt;
         Nothing


&lt;span class="gi"&gt;+updateScores : Score -&amp;gt; Player -&amp;gt; Score&lt;/span&gt;
&lt;span class="gi"&gt;+updateScores score winner =&lt;/span&gt;
&lt;span class="gi"&gt;+    case winner of&lt;/span&gt;
&lt;span class="gi"&gt;+        RightPlayer -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            { score | rightPlayerScore = score.rightPlayerScore + 1 }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        LeftPlayer -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            { score | leftPlayerScore = score.leftPlayerScore + 1 }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 view : Model -&amp;gt; Svg.Svg Msg
 view { ball, rightPaddle, leftPaddle } =
     svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/84d0867a619fde6032c5563e28542606ff0ecd4a"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now that we have the score, we can display it ;)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;@@ -188,7 +188,6 @@ update msg model =&lt;/span&gt;

                                 updatedScore =
                                     updateScores model.score player
&lt;span class="gd"&gt;-                                        |&amp;gt; Debug.log &amp;quot;score&amp;quot;&lt;/span&gt;
                             in
                             ( Winner player, updatedScore, delayCmd )
             in
&lt;span class="gu"&gt;@@ -333,7 +332,7 @@ updateScores score winner =&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;span class="gd"&gt;-view { ball, rightPaddle, leftPaddle } =&lt;/span&gt;
&lt;span class="gi"&gt;+view { ball, rightPaddle, leftPaddle, score } =&lt;/span&gt;
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
&lt;span class="gu"&gt;@@ -343,6 +342,7 @@ view { ball, rightPaddle, leftPaddle } =&lt;/span&gt;
         [ viewBall ball
         , viewPaddle rightPaddle
         , viewPaddle leftPaddle
&lt;span class="gi"&gt;+        , viewScore score&lt;/span&gt;
         ]


&lt;span class="gu"&gt;@@ -376,6 +376,19 @@ viewPaddle paddle =&lt;/span&gt;
         []


&lt;span class="gi"&gt;+viewScore : Score -&amp;gt; Svg.Svg Msg&lt;/span&gt;
&lt;span class="gi"&gt;+viewScore score =&lt;/span&gt;
&lt;span class="gi"&gt;+    g&lt;/span&gt;
&lt;span class="gi"&gt;+        [ fontSize &amp;quot;100px&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , fontFamily &amp;quot;monospace&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;span class="gi"&gt;+        [ text_ [ x &amp;quot;100&amp;quot;, y &amp;quot;100&amp;quot;, textAnchor &amp;quot;start&amp;quot; ]&lt;/span&gt;
&lt;span class="gi"&gt;+            [ text &amp;lt;| String.fromInt score.leftPlayerScore ]&lt;/span&gt;
&lt;span class="gi"&gt;+        , text_ [ x &amp;quot;400&amp;quot;, y &amp;quot;100&amp;quot;, textAnchor &amp;quot;end&amp;quot; ]&lt;/span&gt;
&lt;span class="gi"&gt;+            [ text &amp;lt;| String.fromInt score.rightPlayerScore ]&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 subscriptions : Model -&amp;gt; Sub Msg
 subscriptions model =
     case model.gameStatus of
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/bbb07a70ec9bd4e2b089cfdb4bba16c3df7c117d"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/11-display-score"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tada!!!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Displaying the score" src="//mathieu.agopian.info/blog/images/elm-pong_score.png"&gt;&lt;/p&gt;
&lt;p&gt;Maybe it's time to talk briefly about the view. You might have been wondering
how that was working.&lt;/p&gt;
&lt;p&gt;In elm we have packages that provide helpers to build the node tree. There's
one for &lt;a href="https://package.elm-lang.org/packages/elm/html/latest/"&gt;Html&lt;/a&gt; and one
for &lt;a href="https://package.elm-lang.org/packages/elm/svg/latest/Svg"&gt;SVG&lt;/a&gt;, and they
both work the same way.
Each node builder has two parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a list of attributes and event listeners&lt;/li&gt;
&lt;li&gt;a list of child nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One notable exception is the &lt;code&gt;text&lt;/code&gt; helper which just returns plain text.&lt;/p&gt;
&lt;p&gt;This is how you would build a paragraph with the &lt;code&gt;foobar&lt;/code&gt; class:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Attributes&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;class&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;foobar&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hello world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So building a node tree (a DOM) is a matter of calling helpers and providing
them their list of attributes and children.&lt;/p&gt;
&lt;p&gt;And that's exactly what we did with the &lt;code&gt;viewScore&lt;/code&gt; function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the parent node is an SVG &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt; container, with a list of attributes&lt;ul&gt;
&lt;li&gt;first child is an SVG &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; node with its list of attributes&lt;ul&gt;
&lt;li&gt;and its child is just plain text&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;second child is an SVG &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; node with its list of attributes&lt;ul&gt;
&lt;li&gt;and its child is just plain text&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The use of an underscore in &lt;code&gt;text_&lt;/code&gt; is needed to disambiguate between the
&lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; SVG node and the plain text helper &lt;code&gt;text&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We now have a fully playable game! It might not be pretty, or fun, but it has
all the minimal requirements, congratulations!&lt;/p&gt;
&lt;p&gt;Next time, we might have a look at how to add a few bells and whistles just for
the sake of introducing a few more elm concepts ;)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There's now a &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-4.html"&gt;follow up&lt;/a&gt;.&lt;/p&gt;</content><category term="elm"></category><category term="gamedev"></category></entry><entry><title>Making a pong game in elm (2)</title><link href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-2.html" rel="alternate"></link><published>2019-07-30T15:41:00+02:00</published><updated>2019-07-30T15:41:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2019-07-30:/blog/making-a-pong-game-in-elm-2.html</id><summary type="html">&lt;p&gt;Following the &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm.html"&gt;previous blog post&lt;/a&gt;,
let's continue taking tiny steps in our endeavour to create a pong game in elm.&lt;/p&gt;
&lt;p&gt;We left off with a ball and a single paddle. The ball would move towards the
right, bounce off the paddle, and then move left until it left the screen …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Following the &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm.html"&gt;previous blog post&lt;/a&gt;,
let's continue taking tiny steps in our endeavour to create a pong game in elm.&lt;/p&gt;
&lt;p&gt;We left off with a ball and a single paddle. The ball would move towards the
right, bounce off the paddle, and then move left until it left the screen.&lt;/p&gt;
&lt;h2&gt;Adding a left paddle&lt;/h2&gt;
&lt;p&gt;Before adding a left paddle, let's slightly change our code to prepare for it,
by renaming the current paddle to &lt;code&gt;rightPaddle&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type alias Model =
     { ball : Ball
&lt;span class="gd"&gt;-    , paddle : Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+    , rightPaddle : Paddle&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -38,9 +38,8 @@ type alias Flags =&lt;/span&gt;

 init : Flags -&amp;gt; ( Model, Cmd Msg )
 init _ =
&lt;span class="gd"&gt;-    ( { ball =&lt;/span&gt;
&lt;span class="gd"&gt;-            initBall&lt;/span&gt;
&lt;span class="gd"&gt;-      , paddle = initPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+    ( { ball = initBall&lt;/span&gt;
&lt;span class="gi"&gt;+      , rightPaddle = initPaddle&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -83,7 +82,7 @@ update msg model =&lt;/span&gt;
                     model.ball

                 shouldBounce =
&lt;span class="gd"&gt;-                    shouldBallBounce model.paddle model.ball&lt;/span&gt;
&lt;span class="gi"&gt;+                    shouldBallBounce model.rightPaddle model.ball&lt;/span&gt;

                 horizSpeed =
                     if shouldBounce then
&lt;span class="gu"&gt;@@ -109,7 +108,7 @@ shouldBallBounce paddle ball =&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;span class="gd"&gt;-view { ball, paddle } =&lt;/span&gt;
&lt;span class="gi"&gt;+view { ball, rightPaddle } =&lt;/span&gt;
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
&lt;span class="gu"&gt;@@ -117,7 +116,7 @@ view { ball, paddle } =&lt;/span&gt;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
         [ viewBall ball
&lt;span class="gd"&gt;-        , viewPaddle paddle&lt;/span&gt;
&lt;span class="gi"&gt;+        , viewPaddle rightPaddle&lt;/span&gt;
         ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/f8d33e5916ffc1445003e0d3c2df6c9efb6e9d0b"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Adding the left paddle should now be very straightforward:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type alias Model =
     { ball : Ball
     , rightPaddle : Paddle
&lt;span class="gi"&gt;+    , leftPaddle : Paddle&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -40,6 +41,7 @@ init : Flags -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 init _ =
     ( { ball = initBall
       , rightPaddle = initPaddle
&lt;span class="gi"&gt;+      , leftPaddle = initPaddle&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -108,7 +110,7 @@ shouldBallBounce paddle ball =&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;span class="gd"&gt;-view { ball, rightPaddle } =&lt;/span&gt;
&lt;span class="gi"&gt;+view { ball, rightPaddle, leftPaddle } =&lt;/span&gt;
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
&lt;span class="gu"&gt;@@ -117,6 +119,7 @@ view { ball, rightPaddle } =&lt;/span&gt;
         ]
         [ viewBall ball
         , viewPaddle rightPaddle
&lt;span class="gi"&gt;+        , viewPaddle leftPaddle&lt;/span&gt;
         ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/82706931c5682434fb638b0cd94004e46af03311"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Yes, you clever person, I know what you're thinking: "we can't see the left
paddle! And it's obvious, it's because you placed it exactly at the same
position as the right paddle!". I'm proud of you, and yes, you are right.
Let's fix that by modifying the &lt;code&gt;initPaddle&lt;/code&gt; function which should now take an
initial &lt;code&gt;x&lt;/code&gt; position.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; init : Flags -&amp;gt; ( Model, Cmd Msg )
 init _ =
     ( { ball = initBall
&lt;span class="gd"&gt;-      , rightPaddle = initPaddle&lt;/span&gt;
&lt;span class="gd"&gt;-      , leftPaddle = initPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+      , rightPaddle = initPaddle 480&lt;/span&gt;
&lt;span class="gi"&gt;+      , leftPaddle = initPaddle 10&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -56,9 +56,9 @@ initBall =&lt;/span&gt;
     }


&lt;span class="gd"&gt;-initPaddle : Paddle&lt;/span&gt;
&lt;span class="gd"&gt;-initPaddle =&lt;/span&gt;
&lt;span class="gd"&gt;-    { x = 480&lt;/span&gt;
&lt;span class="gi"&gt;+initPaddle : Int -&amp;gt; Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+initPaddle initialX =&lt;/span&gt;
&lt;span class="gi"&gt;+    { x = initialX&lt;/span&gt;
     , y = 225
     , width = 10
     , height = 50
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/3bb0e3e519785f12741c398d73315ffc57da0ef1"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Left paddle with the ball moving towards it" src="//mathieu.agopian.info/blog/images/elm-pong_left_paddle.png"&gt;&lt;/p&gt;
&lt;p&gt;"But wait Mathieu, can't you see the ball is going right through the left
paddle!". I sure do, and yes, let's now fix that by changing the
&lt;code&gt;shouldBallBounce&lt;/code&gt; helper function.&lt;/p&gt;
&lt;p&gt;It'll be a bit tricky though, because we need to check both paddles slightly
differently: if it's the left paddle, the check on the &lt;code&gt;y&lt;/code&gt; position is exactly
the same, but the check on the &lt;code&gt;x&lt;/code&gt; position now needs to make sure that the
ball stays "right of" the left paddle, which means that the ball's center minus
its radius is bigger than the left paddle's &lt;code&gt;x&lt;/code&gt; position plus its width.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                 shouldBounce =
                     shouldBallBounce model.rightPaddle model.ball
&lt;span class="gi"&gt;+                        || shouldBallBounce model.leftPaddle model.ball&lt;/span&gt;

                 horizSpeed =
                     if shouldBounce then
&lt;span class="gu"&gt;@@ -104,9 +105,17 @@ update msg model =&lt;/span&gt;

 shouldBallBounce : Paddle -&amp;gt; Ball -&amp;gt; Bool
 shouldBallBounce paddle ball =
&lt;span class="gd"&gt;-    (ball.x + ball.radius &amp;gt;= paddle.x)&lt;/span&gt;
&lt;span class="gd"&gt;-        &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y)&lt;/span&gt;
&lt;span class="gd"&gt;-        &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50)&lt;/span&gt;
&lt;span class="gi"&gt;+    if paddle.x == 10 then&lt;/span&gt;
&lt;span class="gi"&gt;+        -- left paddle&lt;/span&gt;
&lt;span class="gi"&gt;+        (ball.x - ball.radius &amp;lt;= paddle.x + paddle.width)&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y)&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+    else&lt;/span&gt;
&lt;span class="gi"&gt;+        -- right paddle&lt;/span&gt;
&lt;span class="gi"&gt;+        (ball.x + ball.radius &amp;gt;= paddle.x)&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y)&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/4be1df966bd9350ffbd8718896c2157345e47847"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It's now working exactly as expected:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ball bouncing between the right and left paddle" src="//mathieu.agopian.info/blog/images/elm-pong_bouncing_ball.gif"&gt;&lt;/p&gt;
&lt;p&gt;That's a huge success, let's take a pause, and reflect on our awesomeness!&lt;/p&gt;
&lt;h2&gt;Refactoring and types&lt;/h2&gt;
&lt;p&gt;If you're like me, you feel that there's something smelly. Something fishy.
Something that isn't quite right.&lt;/p&gt;
&lt;p&gt;I mean, what's with the &lt;code&gt;shouldBallBounce&lt;/code&gt; function and its check on the
paddle's &lt;code&gt;x&lt;/code&gt; position? Sure, there's a comment in there, which is a bit like a
perfume spread on something smelly: it doesn't make the smelly thing less
smelly, it just kinda hides the smell.
And I've written "smell" way too often in the last couple sentences (see the
&lt;a href="https://en.wikipedia.org/wiki/Code_smell"&gt;code smell definition&lt;/a&gt; for the
reference).&lt;/p&gt;
&lt;p&gt;Instead of checking the &lt;code&gt;x&lt;/code&gt; position of a paddle to know if it's the left or
the right one, it would be very useful to declare the paddles as &lt;code&gt;left&lt;/code&gt; or
&lt;code&gt;right&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In other languages, people would use something like an &lt;code&gt;enum&lt;/code&gt;, but in elm,
there's a wonderful thing called
&lt;a href="https://guide.elm-lang.org/types/custom_types.html"&gt;custom types&lt;/a&gt; which are
very powerful and convenient to use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Paddle&lt;/span&gt;
    &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LeftPaddle&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;RightPaddle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Actually, we need the position information (&lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;) for
each paddle, so it should rather be something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Paddle&lt;/span&gt;
    &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LeftPaddle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;RightPaddle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By now, this thing might ring a bell to you: in the previous installment we
declared another custom type (the &lt;code&gt;Msg&lt;/code&gt;) which also encapsulated some data.
It did ring a bell to you, didn't it? You're clever, and I'm proud of you. You
should also be proud of yourself. And even if it didn't ring a bell to you, I'm
sure it'll will in the future, and I'm proud of you all the same!&lt;/p&gt;
&lt;p&gt;So what are those custom types? It might be one of the features of elm that I
find the most difficult to put in words, even though they feel so natural now
that I'm used to them.&lt;/p&gt;
&lt;p&gt;They're composed of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a name: the type itself (&lt;code&gt;Msg&lt;/code&gt;, &lt;code&gt;Paddle&lt;/code&gt;, ...)&lt;/li&gt;
&lt;li&gt;some variants: you can see those as functions, or constructors. They are the
  different "values" for this type. For the &lt;code&gt;Msg&lt;/code&gt; type we only declared one. For
  the &lt;code&gt;Paddle&lt;/code&gt; type we'll have two (&lt;code&gt;LeftPaddle&lt;/code&gt; and &lt;code&gt;RightPaddle&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;some optional data attached to those variants: the &lt;code&gt;OnAnimationFrame&lt;/code&gt; variant
  had a float, our &lt;code&gt;LeftPaddle&lt;/code&gt; and &lt;code&gt;RightPaddle&lt;/code&gt; have some position
  information.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regarding the optional data attached, you can have variants with and without
those. This is how you could type an answer which is either yes, no, or
other with some additional information.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Answer&lt;/span&gt;
    &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Yes&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;No&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Other&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here we used the built-in String type for the associated information, but we
can use any type, including one we've defined ourselves&lt;/p&gt;
&lt;p&gt;Oh, another cool thing, a variant can have any number of data attached to it,
so we could imagine having&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;Paddle&lt;/span&gt;
    &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LeftPaddle&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
    &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;RightPaddle&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;But then we'd have to remember which &lt;code&gt;Int&lt;/code&gt; is for which type of data, which
would be inconvenient, more difficult to maintain, and generally seen as bad
practice.&lt;/p&gt;
&lt;p&gt;Soooooo, after all this chatter, let's&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;declare a &lt;code&gt;PaddleInfo&lt;/code&gt; type alias to hold all the position data&lt;/li&gt;
&lt;li&gt;attach this &lt;code&gt;PaddleInfo&lt;/code&gt; to the modified &lt;code&gt;Paddle&lt;/code&gt; type variants&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-type alias Paddle =&lt;/span&gt;
&lt;span class="gi"&gt;+type Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+    = RightPaddle PaddleInfo&lt;/span&gt;
&lt;span class="gi"&gt;+    | LeftPaddle PaddleInfo&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+type alias PaddleInfo =&lt;/span&gt;
     { x : Int
     , y : Int
     , width : Int
     , height: Int
     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And now for arguably the best part of elm: the compiler. Let's compile this,
and follow the errors that the compiler is giving us, and we'll have everything
compiling and working again in a jiffy.&lt;/p&gt;
&lt;p&gt;The first error says:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-- TYPE MISMATCH -------------------------------------------------- src/Main.elm

Something is off with the body of the &lt;span class="sb"&gt;`&lt;/span&gt;initPaddle&lt;span class="sb"&gt;`&lt;/span&gt; definition:

&lt;span class="m"&gt;66&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; initialX
&lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    , &lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;225&lt;/span&gt;
&lt;span class="m"&gt;68&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    , &lt;span class="nv"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="m"&gt;69&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    , &lt;span class="nv"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;span class="m"&gt;70&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    &lt;span class="o"&gt;}&lt;/span&gt;

The body is a record of type:

    &lt;span class="o"&gt;{&lt;/span&gt; height : number, width : number1, x : Int, y : number2 &lt;span class="o"&gt;}&lt;/span&gt;

But the &lt;span class="nb"&gt;type&lt;/span&gt; annotation on &lt;span class="sb"&gt;`&lt;/span&gt;initPaddle&lt;span class="sb"&gt;`&lt;/span&gt; says it should be:

    Paddle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, the compiler complains because we changed to the &lt;code&gt;Paddle&lt;/code&gt; type, and the
&lt;code&gt;initPaddle&lt;/code&gt; function signature still says it's returning a &lt;code&gt;Paddle&lt;/code&gt;, but it
should instead be a &lt;code&gt;PaddleInfo&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-initPaddle : Int -&amp;gt; Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+initPaddle : Int -&amp;gt; PaddleInfo&lt;/span&gt;
 initPaddle initialX =
     { x = initialX
     , y = 225
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The next compiler error is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-- TYPE MISMATCH -------------------------------------------------- src/Main.elm

Something is off with the body of the &lt;span class="sb"&gt;`&lt;/span&gt;init&lt;span class="sb"&gt;`&lt;/span&gt; definition:

&lt;span class="m"&gt;47&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    &lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ball&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; initBall
&lt;span class="m"&gt;48&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;      , &lt;span class="nv"&gt;rightPaddle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; initPaddle &lt;span class="m"&gt;480&lt;/span&gt;
&lt;span class="m"&gt;49&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;      , &lt;span class="nv"&gt;leftPaddle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; initPaddle &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;      &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="m"&gt;51&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    , Cmd.none
&lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;    &lt;span class="o"&gt;)&lt;/span&gt;

The body is a tuple of type:

    &lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; ball : Ball, leftPaddle : PaddleInfo, rightPaddle : PaddleInfo &lt;span class="o"&gt;}&lt;/span&gt;
    , Cmd msg
    &lt;span class="o"&gt;)&lt;/span&gt;

But the &lt;span class="nb"&gt;type&lt;/span&gt; annotation on &lt;span class="sb"&gt;`&lt;/span&gt;init&lt;span class="sb"&gt;`&lt;/span&gt; says it should be:

    &lt;span class="o"&gt;(&lt;/span&gt; Model, Cmd Msg &lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What this says is that the &lt;code&gt;init&lt;/code&gt; function should return a &lt;code&gt;( Model, Cmd Msg )&lt;/code&gt;
but it returns something else instead of the &lt;code&gt;Model&lt;/code&gt;: a record that has two
fields &lt;code&gt;rightPaddle&lt;/code&gt; and &lt;code&gt;leftPaddle&lt;/code&gt; that are... &lt;code&gt;PaddleInfo&lt;/code&gt; (instead of
&lt;code&gt;Paddle&lt;/code&gt;). Let's change this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; init : Flags -&amp;gt; ( Model, Cmd Msg )
 init _ =
     ( { ball = initBall
&lt;span class="gd"&gt;-      , rightPaddle = initPaddle 480&lt;/span&gt;
&lt;span class="gd"&gt;-      , leftPaddle = initPaddle 10&lt;/span&gt;
&lt;span class="gi"&gt;+      , rightPaddle = RightPaddle &amp;lt;| initPaddle 480&lt;/span&gt;
&lt;span class="gi"&gt;+      , leftPaddle = LeftPaddle &amp;lt;| initPaddle 10&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As a refresher, the &lt;code&gt;&amp;lt;|&lt;/code&gt; syntactic sugar means that we're taking the result of
what's on the right of the "arrow", and using it as an argument for what's on
the left. So we're using the &lt;code&gt;PaddleInfo&lt;/code&gt; we're getting back from the
&lt;code&gt;initPaddle&lt;/code&gt; helper function, and "attaching" it to the &lt;code&gt;Paddle&lt;/code&gt; variant.
This could also be rewritten&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;RightPaddle&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;initPaddle&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The next error is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-- TYPE MISMATCH -------------------------------------------------- src/Main.elm

This is not a record, so it has no fields to access!

&lt;span class="m"&gt;113&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="k"&gt;if&lt;/span&gt; paddle.x &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
            ^^^^^^
This &lt;span class="sb"&gt;`&lt;/span&gt;paddle&lt;span class="sb"&gt;`&lt;/span&gt; value is a:

    Paddle

But I need a record with a x field!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is interesting: the line &lt;code&gt;113&lt;/code&gt; is in the &lt;code&gt;shouldBallBounce&lt;/code&gt; function,
which is where we started all this refactoring. We're now ready to reap the
benefits:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; shouldBallBounce : Paddle -&amp;gt; Ball -&amp;gt; Bool
 shouldBallBounce paddle ball =
&lt;span class="gd"&gt;-    if paddle.x == 10 then&lt;/span&gt;
&lt;span class="gd"&gt;-        -- left paddle&lt;/span&gt;
&lt;span class="gd"&gt;-        (ball.x - ball.radius &amp;lt;= paddle.x + paddle.width)&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y)&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50)&lt;/span&gt;
&lt;span class="gd"&gt;-&lt;/span&gt;
&lt;span class="gd"&gt;-    else&lt;/span&gt;
&lt;span class="gd"&gt;-        -- right paddle&lt;/span&gt;
&lt;span class="gd"&gt;-        (ball.x + ball.radius &amp;gt;= paddle.x)&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y)&lt;/span&gt;
&lt;span class="gd"&gt;-            &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50)&lt;/span&gt;
&lt;span class="gi"&gt;+    case paddle of&lt;/span&gt;
&lt;span class="gi"&gt;+        LeftPaddle { x, y, width, height } -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            (ball.x - ball.radius &amp;lt;= x + width)&lt;/span&gt;
&lt;span class="gi"&gt;+                &amp;amp;&amp;amp; (ball.y &amp;gt;= y)&lt;/span&gt;
&lt;span class="gi"&gt;+                &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        RightPaddle { x, y, height } -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            (ball.x + ball.radius &amp;gt;= x)&lt;/span&gt;
&lt;span class="gi"&gt;+                &amp;amp;&amp;amp; (ball.y &amp;gt;= y)&lt;/span&gt;
&lt;span class="gi"&gt;+                &amp;amp;&amp;amp; (ball.y &amp;lt;= y + height)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;"MATHIEU?! WHAT KIND OF VOODOO IS THAT, YOU TRICKED ME! I thought this was going
to be an easy to follow guide, and now I feel miserable!"&lt;/p&gt;
&lt;p&gt;I'm sorry, please bear with me for a minute while I dissect what happened here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we already saw the &lt;code&gt;case&lt;/code&gt; in the previous blog post when writing the &lt;code&gt;update&lt;/code&gt;
  function. It's like a &lt;code&gt;switch&lt;/code&gt; in some other languages, and it's used to
  "select" which branch of code to execute given what the "shape" of the data
  is. Here we're selecting based on the variants of &lt;code&gt;Paddle&lt;/code&gt; (&lt;code&gt;LeftPaddle&lt;/code&gt; or
  &lt;code&gt;RightPaddle&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;case&lt;/code&gt; also allows us to
  &lt;a href="https://gist.github.com/yang-wei/4f563fbf81ff843e8b1e"&gt;"destructure" or "pattern match"&lt;/a&gt;
  the type and its attached data. What we're saying is "if there's a
  &lt;code&gt;LeftPaddle&lt;/code&gt; then assign its &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; to variables of
  the same name. So in the code after the &lt;code&gt;-&amp;gt;&lt;/code&gt; the names &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, ... all have
  the value of what's in the &lt;code&gt;PaddleInfo&lt;/code&gt; record attached to the &lt;code&gt;Paddle&lt;/code&gt; type.&lt;/li&gt;
&lt;li&gt;in the case of the &lt;code&gt;RightPaddle&lt;/code&gt; we didn't need the &lt;code&gt;width&lt;/code&gt; value, so we
  simply didn't "destructure" (and assign) it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This new code might not be much shorter, but it's way more readable, and
doesn't involve a &lt;code&gt;paddle.x&lt;/code&gt; check against a hard coded value that we might
want to change in the future. Also no more need for the comments, as it's self
commenting.&lt;/p&gt;
&lt;p&gt;And we're left with one last error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-- TYPE MISMATCH -------------------------------------------------- src/Main.elm

This is not a record, so it has no fields to access!

&lt;span class="m"&gt;152&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;         &lt;span class="o"&gt;[&lt;/span&gt; x &amp;lt;&lt;span class="p"&gt;|&lt;/span&gt; String.fromInt paddle.x
                                   ^^^^^^
This &lt;span class="sb"&gt;`&lt;/span&gt;paddle&lt;span class="sb"&gt;`&lt;/span&gt; value is a:

    Paddle

But I need a record with a x field!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The fix is then:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; viewPaddle : Paddle -&amp;gt; Svg.Svg Msg
 viewPaddle paddle =
&lt;span class="gi"&gt;+    let&lt;/span&gt;
&lt;span class="gi"&gt;+        paddleInfo =&lt;/span&gt;
&lt;span class="gi"&gt;+            case paddle of&lt;/span&gt;
&lt;span class="gi"&gt;+                LeftPaddle info -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    info&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                RightPaddle info -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    info&lt;/span&gt;
&lt;span class="gi"&gt;+    in&lt;/span&gt;
     rect
&lt;span class="gd"&gt;-        [ x &amp;lt;| String.fromInt paddle.x&lt;/span&gt;
&lt;span class="gd"&gt;-        , y &amp;lt;| String.fromInt paddle.y&lt;/span&gt;
&lt;span class="gd"&gt;-        , width &amp;lt;| String.fromInt paddle.width&lt;/span&gt;
&lt;span class="gd"&gt;-        , height &amp;lt;| String.fromInt paddle.height&lt;/span&gt;
&lt;span class="gi"&gt;+        [ x &amp;lt;| String.fromInt paddleInfo.x&lt;/span&gt;
&lt;span class="gi"&gt;+        , y &amp;lt;| String.fromInt paddleInfo.y&lt;/span&gt;
&lt;span class="gi"&gt;+        , width &amp;lt;| String.fromInt paddleInfo.width&lt;/span&gt;
&lt;span class="gi"&gt;+        , height &amp;lt;| String.fromInt paddleInfo.height&lt;/span&gt;
         ]
         []
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;"MATHIEU?! YOU DID IT AGAIN!". Ok, sorry, sorry, I was being cheeky here.&lt;/p&gt;
&lt;p&gt;The main part is this one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;    &lt;span class="kr"&gt;let&lt;/span&gt;
        &lt;span class="nv"&gt;paddleInfo&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt;
            &lt;span class="kr"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;paddle&lt;/span&gt; &lt;span class="kr"&gt;of&lt;/span&gt;
                &lt;span class="kt"&gt;LeftPaddle&lt;/span&gt; &lt;span class="nv"&gt;info&lt;/span&gt; &lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="nv"&gt;info&lt;/span&gt;

                &lt;span class="kt"&gt;RightPaddle&lt;/span&gt; &lt;span class="nv"&gt;info&lt;/span&gt; &lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="nv"&gt;info&lt;/span&gt;
    &lt;span class="kr"&gt;in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;let..in&lt;/code&gt; is where we assign values to names (to "variables"). Here we're
assigning the attached data of the &lt;code&gt;LeftPaddle&lt;/code&gt; or &lt;code&gt;RightPaddle&lt;/code&gt; to the name
&lt;code&gt;paddleInfo&lt;/code&gt; (which we're using in the rest of the function code).
And we're once again using a &lt;code&gt;case&lt;/code&gt; to destructure the type. Both cases are the
same because in our case both variants of the &lt;code&gt;Paddle&lt;/code&gt; custom type have a
single attached data of type &lt;code&gt;PaddleInfo&lt;/code&gt;, but it could be different: as we
explained earlier, we can mix and match any kind of variants for a given custom
type.&lt;/p&gt;
&lt;p&gt;So when we write &lt;code&gt;LeftPaddle info -&amp;gt; info&lt;/code&gt; we're saying "if it's a &lt;code&gt;LeftPaddle&lt;/code&gt;
then take its attached data and assign it to the name "info", then return this
"info" value" (which we then assign to the &lt;code&gt;paddleInfo&lt;/code&gt; name in the &lt;code&gt;let..in&lt;/code&gt;
clause).&lt;/p&gt;
&lt;p&gt;And we're now done! We have the exact same behavior, but a code that's more
precise and maintainable. I agree that it might look more complex, or sometimes
longer, but it gives us more flexibility, and above all, much better help and
guards from the compiler, which is invaluable.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/a3b0997e911837730e50854e0193e616945b8b53"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/6-two-paddles"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Moving the paddle&lt;/h2&gt;
&lt;p&gt;What good is a game if the player has no control whatsoever on it? It's high
time that we give a way for the player to move the paddle. We'll start with the
right paddle, just because.&lt;/p&gt;
&lt;p&gt;This can't be hard, right? RIGHT? WRONG!&lt;/p&gt;
&lt;p&gt;Sometimes elm feel complex. Complicated. Overengineered. Hard. Difficult. And
that is because it's trying to do the right thing, not the simple thing. It's
trying very very hard to prevent you from shooting yourself in the foot. It's
helping you not have any runtime errors.&lt;/p&gt;
&lt;p&gt;One such case are
&lt;a href="https://guide.elm-lang.org/effects/json.html"&gt;JSON decoders&lt;/a&gt;. And those are
probably one of the major hurdles that elm beginners will face at some point,
and feel overwhelmed.&lt;/p&gt;
&lt;h3&gt;Why decoders?&lt;/h3&gt;
&lt;p&gt;If they are so difficult, why bother at all? I mean, JSON is easy, right?
Strings, bools, arrays, objects, numbers... how hard can it be?&lt;/p&gt;
&lt;p&gt;Let's take a step back for a moment: if the elm compiler is going to help you
have no runtime exception, it needs to be able to guarantee that there's no bad
code branches. That a given field in the JSON object you're getting back is
indeed a number, and not a string, or an array, or a null. Because if it can't
guarantee that a value is of the proper type, it can't guarantee that the
operations you do on that value are valid.&lt;/p&gt;
&lt;p&gt;So, if the elm compiler needs to know the type of the fields in the JSON it
gets, it needs a way to "decode" this JSON into proper types. And if the JSON
doesn't decode properly into those types, then it'll fail in an expected way,
and make you write a code branch for that case, so it doesn't end blowing up in
your face at runtime.&lt;/p&gt;
&lt;p&gt;That's where and why decoders are useful: you provide a "translation" from a
untyped JSON to a typed value: if it succeeds, then you can use that typed
value. If it fails, you handle this case (by displaying an error message for
example).&lt;/p&gt;
&lt;p&gt;You can see that as a way to validate the JSON that elm is receiving from the
javascript land in the case of an event, or from an http request to a remote
API.&lt;/p&gt;
&lt;p&gt;So yes, decoders are hard to grasp. But if you trust my own experience, once
you get to use them, you'll be missing them in other languages, and hoping
there was a way to achieve the same result.&lt;/p&gt;
&lt;h3&gt;Keyboard events&lt;/h3&gt;
&lt;p&gt;There's a convenient
&lt;a href="https://package.elm-lang.org/packages/elm/browser/latest/Browser-Events#keyboard"&gt;Browser.Event module&lt;/a&gt;
that provides event listeners for keyboard events.&lt;/p&gt;
&lt;p&gt;Remember when we subscribed to the &lt;code&gt;Browser.Events.onAnimationFrameDelta&lt;/code&gt;
events in the previous episode? We'll do basically the same this time around.&lt;/p&gt;
&lt;p&gt;We need to do a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decode a &lt;code&gt;String&lt;/code&gt; from the keydown event&lt;/li&gt;
&lt;li&gt;Attach that string to a &lt;code&gt;Msg&lt;/code&gt; variant that we'll add: &lt;code&gt;KeyDown String&lt;/code&gt;, so
  the decoder returns a &lt;code&gt;Decoder Msg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Provide that decoder to the
  &lt;a href="https://package.elm-lang.org/packages/elm/browser/latest/Browser-Events#onKeyDown"&gt;Browser.Events.onKeyDown&lt;/a&gt;
  subscription&lt;/li&gt;
&lt;li&gt;Add a branch to the &lt;code&gt;case&lt;/code&gt; in the &lt;code&gt;update&lt;/code&gt; function to deal with the new
  &lt;code&gt;Msg&lt;/code&gt; variant (we'll only display some debug info in the console for now)&lt;/li&gt;
&lt;li&gt;Add an import to the &lt;code&gt;Json.Decode&lt;/code&gt; module&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; import Browser
 import Browser.Events
&lt;span class="gi"&gt;+import Json.Decode as Decode&lt;/span&gt;
 import Svg exposing (..)
 import Svg.Attributes exposing (..)

&lt;span class="gu"&gt;@@ -36,6 +37,7 @@ type alias PaddleInfo =&lt;/span&gt;

 type Msg
     = OnAnimationFrame Float
&lt;span class="gi"&gt;+    | KeyDown String&lt;/span&gt;


 type alias Flags =
&lt;span class="gu"&gt;@@ -107,6 +109,13 @@ update msg model =&lt;/span&gt;
             in
             ( { model | ball = updatedBall }, Cmd.none )

&lt;span class="gi"&gt;+        KeyDown keyString -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            let&lt;/span&gt;
&lt;span class="gi"&gt;+                _ =&lt;/span&gt;
&lt;span class="gi"&gt;+                    Debug.log &amp;quot;key pressed&amp;quot; keyString&lt;/span&gt;
&lt;span class="gi"&gt;+            in&lt;/span&gt;
&lt;span class="gi"&gt;+            ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;

 shouldBallBounce : Paddle -&amp;gt; Ball -&amp;gt; Bool
 shouldBallBounce paddle ball =
&lt;span class="gu"&gt;@@ -168,4 +177,12 @@ viewPaddle paddle =&lt;/span&gt;

 subscriptions : Model -&amp;gt; Sub Msg
 subscriptions _ =
&lt;span class="gd"&gt;-    Browser.Events.onAnimationFrameDelta OnAnimationFrame&lt;/span&gt;
&lt;span class="gi"&gt;+    Sub.batch&lt;/span&gt;
&lt;span class="gi"&gt;+        [ Browser.Events.onAnimationFrameDelta OnAnimationFrame&lt;/span&gt;
&lt;span class="gi"&gt;+        , Browser.Events.onKeyDown (Decode.map KeyDown keyDecoder)&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+keyDecoder : Decode.Decoder String&lt;/span&gt;
&lt;span class="gi"&gt;+keyDecoder =&lt;/span&gt;
&lt;span class="gi"&gt;+    Decode.field &amp;quot;key&amp;quot; Decode.string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/8555850f4eb96cc781eb0bd512a5415cb4663168"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We used &lt;code&gt;Decode.map&lt;/code&gt; here to attach the string we get back from the decoder to
the &lt;code&gt;KeyDown&lt;/code&gt; variant... but only if the &lt;code&gt;keyDecoder&lt;/code&gt; succeeded!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Decode.map&lt;/code&gt; says "if the decoder succeeds, apply this function to the result".
And we needed to do that because the &lt;code&gt;onKeyDown&lt;/code&gt; event listener needs a
&lt;code&gt;Decoder Msg&lt;/code&gt; and not a &lt;code&gt;Decoder String&lt;/code&gt;. In order to be able to subscribe to
the events, the listener needs to know which message to call when it receives
an event.&lt;/p&gt;
&lt;p&gt;If you followed along, you'll notice a compiler error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-- UNKNOWN IMPORT ------------------------------------------------- src/Main.elm

The Main module has a bad import:

    import Json.Decode

Do you want the one from the elm/json package? If so, run this &lt;span class="nb"&gt;command&lt;/span&gt; to add
that dependency to your elm.json file:

    elm install elm/json

If you want a &lt;span class="nb"&gt;local&lt;/span&gt; file, make sure the &lt;span class="sb"&gt;`&lt;/span&gt;Json.Decode&lt;span class="sb"&gt;`&lt;/span&gt; module is in one of the
&lt;span class="s2"&gt;&amp;quot;source-directories&amp;quot;&lt;/span&gt; listed in your elm.json file.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This very helpful message tells us that we need to add the missing dependency
to the &lt;code&gt;elm.json&lt;/code&gt; file, just like we did with &lt;code&gt;elm/svg&lt;/code&gt; in the previous post.
Running the &lt;code&gt;elm install elm/json&lt;/code&gt; command result in the following changes in
the &lt;code&gt;elm.json&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;             &amp;quot;elm/browser&amp;quot;: &amp;quot;1.0.1&amp;quot;,
             &amp;quot;elm/core&amp;quot;: &amp;quot;1.0.2&amp;quot;,
             &amp;quot;elm/html&amp;quot;: &amp;quot;1.0.0&amp;quot;,
&lt;span class="gi"&gt;+            &amp;quot;elm/json&amp;quot;: &amp;quot;1.1.3&amp;quot;,&lt;/span&gt;
             &amp;quot;elm/svg&amp;quot;: &amp;quot;1.0.1&amp;quot;
         },
         &amp;quot;indirect&amp;quot;: {
&lt;span class="gd"&gt;-            &amp;quot;elm/json&amp;quot;: &amp;quot;1.1.3&amp;quot;,&lt;/span&gt;
             &amp;quot;elm/time&amp;quot;: &amp;quot;1.0.0&amp;quot;,
             &amp;quot;elm/url&amp;quot;: &amp;quot;1.0.0&amp;quot;,
             &amp;quot;elm/virtual-dom&amp;quot;: &amp;quot;1.0.2&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It moved the &lt;code&gt;elm/json&lt;/code&gt; dependency from the indirect dependencies to the
direct ones.&lt;/p&gt;
&lt;p&gt;In the previous post we already used the &lt;code&gt;Debug.log&lt;/code&gt; helper, so let's take a
minute to explain what that does: it's a function takes any argument (in our
case the &lt;code&gt;keyString&lt;/code&gt;) and displays it in the browser console, preceeded by the
message you provide it (eg &lt;code&gt;key pressed&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is the result of pressing a few keys in the console:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Debug.log messages in the console" src="//mathieu.agopian.info/blog/images/elm-pong_debug_log.png"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Debug.log&lt;/code&gt; helper also returns exactly what it received, so you can add it
anywhere, it's very convenient!&lt;/p&gt;
&lt;p&gt;However, be aware that it's not possible to use the elm compiler optimization
with a &lt;code&gt;Debug.xxx&lt;/code&gt; call, so they need to be removed before you compile for
production.&lt;/p&gt;
&lt;p&gt;In our case, we wanted to output a debug message to the console, but not do
anything else. A common pattern is to use a &lt;code&gt;let..in&lt;/code&gt; to fake a variable
assignment, but not care about the resulting variable (hence the &lt;code&gt;_ = ...&lt;/code&gt;). As
we're not doing anything with the keyboard event (yet), we're also returning
the exact same model we received in the update function, and send no commands:
&lt;code&gt;( model, Cmd.none)&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Decoding arrow key presses&lt;/h3&gt;
&lt;p&gt;There's a very convenient link in the &lt;code&gt;onKeyDown&lt;/code&gt; documentation that
brings us straight to something of great interest to us: &lt;a href="https://github.com/elm/browser/blob/1.0.0/notes/keyboard.md#decoding-for-games"&gt;Decoding for
games&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As explained, it would make a lot of sense and give us some guarantees to use
a custom type (that we could call &lt;code&gt;PlayerAction&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type Msg
     = OnAnimationFrame Float
&lt;span class="gd"&gt;-    | KeyDown String&lt;/span&gt;
&lt;span class="gi"&gt;+    | KeyDown PlayerAction&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+type PlayerAction&lt;/span&gt;
&lt;span class="gi"&gt;+    = RightPaddleUp&lt;/span&gt;
&lt;span class="gi"&gt;+    | RightPaddleDown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice here that we didn't add an &lt;code&gt;Other&lt;/code&gt; variant, because we'll be doing
something else with the decoder... Event listeners in elm have a nice (and
sometimes confusing?) behavior: whenever the decoder that they're given fails
decoding, the event is simply discarded. Which means that no &lt;code&gt;Msg&lt;/code&gt; is sent.
It's as if the program simply didn't subscribe to those.&lt;/p&gt;
&lt;p&gt;So we're going to modify our decoder to pass the resulting decoded string to
another decoder using the &lt;code&gt;Decode.andThen&lt;/code&gt; helper. And from this second
decoder, we're going to &lt;code&gt;Decode.succeed&lt;/code&gt; with one of the &lt;code&gt;PlayerAction&lt;/code&gt;
variants, or &lt;code&gt;Decode.fail&lt;/code&gt;, in which case the event will be discarded.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-keyDecoder : Decode.Decoder String&lt;/span&gt;
&lt;span class="gi"&gt;+keyDecoder : Decode.Decoder PlayerAction&lt;/span&gt;
 keyDecoder =
     Decode.field &amp;quot;key&amp;quot; Decode.string
&lt;span class="gi"&gt;+        |&amp;gt; Decode.andThen keyToPlayerAction&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+keyToPlayerAction : String -&amp;gt; Decode.Decoder PlayerAction&lt;/span&gt;
&lt;span class="gi"&gt;+keyToPlayerAction keyString =&lt;/span&gt;
&lt;span class="gi"&gt;+    case keyString of&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;quot;ArrowUp&amp;quot; -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            Decode.succeed RightPaddleUp&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;quot;ArrowDown&amp;quot; -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            Decode.succeed RightPaddleDown&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        _ -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            Decode.fail &amp;quot;not an event we care about&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/2d859d34f8b2ac2417bfa2b5eb60fe716220bafc"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Decode.andThen&lt;/code&gt; is a bit different than &lt;code&gt;Decoder.map&lt;/code&gt;: it also takes the
result of a successful decoder, but returns a decoder (not a result). Which
means that we can modify a decoder's behavior and make it fail or succeed
instead of just modifying its successful result. In our case we make it fail if
it's not one of the keys we're interested in... even though the &lt;code&gt;keyDecoder&lt;/code&gt;
would successfully decode a string from the event.&lt;/p&gt;
&lt;p&gt;Neat, isn't it? Are you starting to like decoders? Love them even? I do.&lt;/p&gt;
&lt;h3&gt;Moving the right paddle&lt;/h3&gt;
&lt;p&gt;Now that we can detect player actions, we can react to them, and update the
paddle position:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gd"&gt;-        KeyDown keyString -&amp;gt;&lt;/span&gt;
&lt;span class="gd"&gt;-            let&lt;/span&gt;
&lt;span class="gd"&gt;-                _ =&lt;/span&gt;
&lt;span class="gd"&gt;-                    Debug.log &amp;quot;key pressed&amp;quot; keyString&lt;/span&gt;
&lt;span class="gd"&gt;-            in&lt;/span&gt;
&lt;span class="gd"&gt;-            ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+        KeyDown playerAction -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            case playerAction of&lt;/span&gt;
&lt;span class="gi"&gt;+                RightPaddleUp -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | rightPaddle = model.rightPaddle |&amp;gt; updatePaddle -10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                RightPaddleDown -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | rightPaddle = model.rightPaddle |&amp;gt; updatePaddle 10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+updatePaddle : Int -&amp;gt; Paddle -&amp;gt; Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+updatePaddle amount paddle =&lt;/span&gt;
&lt;span class="gi"&gt;+    case paddle of&lt;/span&gt;
&lt;span class="gi"&gt;+        RightPaddle paddleInfo -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            { paddleInfo | y = paddleInfo.y + amount }&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; RightPaddle&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        LeftPaddle paddleInfo -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            { paddleInfo | y = paddleInfo.y + amount }&lt;/span&gt;
&lt;span class="gi"&gt;+                |&amp;gt; LeftPaddle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/94aaa6e68f964635da982a9ca03cd9ad406fcd9a"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As you can see, we used a small helper function &lt;code&gt;updatePaddle&lt;/code&gt; to update the
info of a paddle, moving it up or down by a certain amount of pixels.&lt;/p&gt;
&lt;h3&gt;Moving the left paddle&lt;/h3&gt;
&lt;p&gt;Now that we have everything in place for the right player, it's straightforward
to deal with the left player. We'll use the keys "e" for up, and "d" for down
to cope with both azerty and qwerty keyboards. I'm sorry if this doesn't make
sense for your keyboard, but I'm sure you'll be able to fix it by changing
those keys in your code ;)&lt;/p&gt;
&lt;p&gt;Let's do that step by step with the help from the lovely compiler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type PlayerAction
     = RightPaddleUp
     | RightPaddleDown
&lt;span class="gi"&gt;+    | LeftPaddleUp&lt;/span&gt;
&lt;span class="gi"&gt;+    | LeftPaddleDown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We have one compiler error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;-- MISSING PATTERNS ----------------------------------------------- src/Main.elm

This &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; does not have branches &lt;span class="k"&gt;for&lt;/span&gt; all possibilities:

&lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;            &lt;span class="k"&gt;case&lt;/span&gt; playerAction of
&lt;span class="m"&gt;121&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                RightPaddleUp -&amp;gt;
&lt;span class="m"&gt;122&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                    &lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; model &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;rightPaddle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; model.rightPaddle &lt;span class="p"&gt;|&lt;/span&gt;&amp;gt; updatePaddle -10 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                    , Cmd.none
&lt;span class="m"&gt;124&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="m"&gt;125&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;
&lt;span class="m"&gt;126&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                RightPaddleDown -&amp;gt;
&lt;span class="m"&gt;127&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                    &lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; model &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;rightPaddle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; model.rightPaddle &lt;span class="p"&gt;|&lt;/span&gt;&amp;gt; updatePaddle &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="m"&gt;128&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                    , Cmd.none
&lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&amp;gt;                    &lt;span class="o"&gt;)&lt;/span&gt;

Missing possibilities include:

    LeftPaddleUp
    LeftPaddleDown

I would have to crash &lt;span class="k"&gt;if&lt;/span&gt; I saw one of those. Add branches &lt;span class="k"&gt;for&lt;/span&gt; them!

Hint: If you want to write the code &lt;span class="k"&gt;for&lt;/span&gt; each branch later, use &lt;span class="sb"&gt;`&lt;/span&gt;Debug.todo&lt;span class="sb"&gt;`&lt;/span&gt; as a
placeholder. Read &amp;lt;https://elm-lang.org/0.19.0/missing-patterns&amp;gt; &lt;span class="k"&gt;for&lt;/span&gt; more
guidance on this workflow.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's an easy one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                     , Cmd.none
                     )

&lt;span class="gi"&gt;+                LeftPaddleUp -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | leftPaddle = model.leftPaddle |&amp;gt; updatePaddle -10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                LeftPaddleDown -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+                    ( { model | leftPaddle = model.leftPaddle |&amp;gt; updatePaddle 10 }&lt;/span&gt;
&lt;span class="gi"&gt;+                    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+                    )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;

 updatePaddle : Int -&amp;gt; Paddle -&amp;gt; Paddle
 updatePaddle amount paddle =
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The thing is, once we fixed that the compiler compiles successfully... but
pressing the "e" or "d" keys has no effect whatsoever... elm compiler, why
have you failed me!!!!!&lt;/p&gt;
&lt;p&gt;Remember when we talked about the (sometimes confusing) behavior of event
listeners in elm that are silently ignored if the decoder fails? And remember
that we used that to our advantage to reduce the code a bit instead of dealing
with an &lt;code&gt;Other&lt;/code&gt; variant for the &lt;code&gt;PlayerAction&lt;/code&gt; custom type?&lt;/p&gt;
&lt;p&gt;Well here we are now: it's up to us to remember that we need to update the
decoder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         &amp;quot;ArrowDown&amp;quot; -&amp;gt;
             Decode.succeed RightPaddleDown

&lt;span class="gi"&gt;+        &amp;quot;e&amp;quot; -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            Decode.succeed LeftPaddleUp&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;quot;d&amp;quot; -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            Decode.succeed LeftPaddleDown&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
         _ -&amp;gt;
             Decode.fail &amp;quot;not an event we care about&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/67935ae0c9fd2296103d8f0f9511d7d29fd2e0b8"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/7-move-paddles"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Well, it seems we have a mostly working pong game. Both players can move their
paddles up or down. We would still have a LOOOOOONG way to go to make it a
real, playable and enjoyable game though.&lt;/p&gt;
&lt;p&gt;We did 80% of the work in 20% of the time. We now need 80% of the time to
finish the remaining 20% of the work ;)&lt;/p&gt;
&lt;p&gt;Some of the things that are blatantly missing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;losing/winning/scoring: when the ball moves off the screen the player should lose,
  and the other one should score a point&lt;/li&gt;
&lt;li&gt;clamping the paddles to the screen, instead of letting them wander off&lt;/li&gt;
&lt;li&gt;better input management: at the moment, both players can't keep pressing the
  keys at the same time, one takes precedence over the other one (at least for
  me on Firefox OSX)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some ideas to make it more enjoyable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;deal with acceleration of the paddle&lt;/li&gt;
&lt;li&gt;power ups/downs&lt;/li&gt;
&lt;li&gt;visual effects&lt;/li&gt;
&lt;li&gt;sound effects&lt;/li&gt;
&lt;li&gt;increasing the difficulty (faster moving ball for example, shorter paddles)&lt;/li&gt;
&lt;li&gt;one player mode against the computer&lt;/li&gt;
&lt;li&gt;move the paddle using the mouse instead of the keyboard for a smoother
  experience&lt;/li&gt;
&lt;li&gt;being able to play on a mobile phone/tablet using gestures&lt;/li&gt;
&lt;li&gt;twists on the original concept&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So what did you think? Did this give you a feel for the elm language? Did it
make you want to give it a go for gamedev or webdev?&lt;/p&gt;
&lt;p&gt;I would be curious to see if you come up with completed/improved games of your
own!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There's now a &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-3.html"&gt;follow up&lt;/a&gt;.&lt;/p&gt;</content><category term="elm"></category><category term="gamedev"></category></entry><entry><title>Making a pong game in elm</title><link href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm.html" rel="alternate"></link><published>2019-07-26T15:01:00+02:00</published><updated>2019-07-26T15:01:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2019-07-26:/blog/making-a-pong-game-in-elm.html</id><summary type="html">&lt;p&gt;Let's make a pong game in &lt;a href="https://elm-lang.org/"&gt;elm&lt;/a&gt;, by taking &lt;a href="https://medium.com/@dillonkearns/moving-faster-with-tiny-steps-in-elm-2e6a269e4efc"&gt;tiny
steps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you haven't already, the very first step is to &lt;a href="https://guide.elm-lang.org/install.html"&gt;install
elm&lt;/a&gt;, and to keep in mind that the
folks on &lt;a href="https://elmlang.herokuapp.com/"&gt;the slack channel&lt;/a&gt; are very friendly and
helpful if you ever face an issue that you can't manage …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Let's make a pong game in &lt;a href="https://elm-lang.org/"&gt;elm&lt;/a&gt;, by taking &lt;a href="https://medium.com/@dillonkearns/moving-faster-with-tiny-steps-in-elm-2e6a269e4efc"&gt;tiny
steps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you haven't already, the very first step is to &lt;a href="https://guide.elm-lang.org/install.html"&gt;install
elm&lt;/a&gt;, and to keep in mind that the
folks on &lt;a href="https://elmlang.herokuapp.com/"&gt;the slack channel&lt;/a&gt; are very friendly and
helpful if you ever face an issue that you can't manage to solve on your own.
You are not alone!&lt;/p&gt;
&lt;h2&gt;Create the project&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ elm init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This creates a &lt;code&gt;elm.json&lt;/code&gt; file for you, and an empty &lt;code&gt;src/&lt;/code&gt; folder. Running
&lt;code&gt;elm make&lt;/code&gt; will compile the project and tell you:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ elm make
Dependencies loaded from &lt;span class="nb"&gt;local&lt;/span&gt; cache.
Dependencies ready!
-- NO INPUT --------------------------------------------------------------------

What should I make though? I need more information, like:

    elm make src/Main.elm
    elm make src/This.elm src/That.elm

However many files you give, I will create one JS file out of them.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ok, so let's create a very basic &lt;code&gt;src/Main.elm&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;module &lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="nv"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import &lt;/span&gt;&lt;span class="nc"&gt;Html&lt;/span&gt;


&lt;span class="kr"&gt;main &lt;/span&gt;&lt;span class="nf"&gt;=&lt;/span&gt;
    &lt;span class="kt"&gt;Html&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hello world&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Running &lt;code&gt;elm make src/Main.elm&lt;/code&gt; will compile successfully and create an
&lt;code&gt;index.html&lt;/code&gt; file for us. Opening it in the browser displays a very dull
&lt;em&gt;Hello world&lt;/em&gt; message. Dull, for sure, but still a success!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/0-create-project"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Display a ball&lt;/h2&gt;
&lt;p&gt;We're going to use the &lt;code&gt;elm/svg&lt;/code&gt;, so we need to install it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;elm install elm/svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here's the result in the &lt;code&gt;elm.json&lt;/code&gt; file once elm added the dependency for us:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         &amp;quot;direct&amp;quot;: {
             &amp;quot;elm/browser&amp;quot;: &amp;quot;1.0.1&amp;quot;,
             &amp;quot;elm/core&amp;quot;: &amp;quot;1.0.2&amp;quot;,
&lt;span class="gd"&gt;-            &amp;quot;elm/html&amp;quot;: &amp;quot;1.0.0&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;quot;elm/html&amp;quot;: &amp;quot;1.0.0&amp;quot;,&lt;/span&gt;
&lt;span class="gi"&gt;+            &amp;quot;elm/svg&amp;quot;: &amp;quot;1.0.1&amp;quot;&lt;/span&gt;
         },
         &amp;quot;indirect&amp;quot;: {
             &amp;quot;elm/json&amp;quot;: &amp;quot;1.1.3&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once that's done, let's first draw a playing field (an empty light gray box),
by changing our &lt;code&gt;src/Main.elm&lt;/code&gt; file to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;module &lt;/span&gt;&lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="nv"&gt;exposing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;import &lt;/span&gt;&lt;span class="nc"&gt;Svg&lt;/span&gt; &lt;span class="nv"&gt;exposing&lt;/span&gt; &lt;span class="nf"&gt;(..)&lt;/span&gt;
&lt;span class="kn"&gt;import &lt;/span&gt;&lt;span class="nc"&gt;Svg.Attributes&lt;/span&gt; &lt;span class="nv"&gt;exposing&lt;/span&gt; &lt;span class="nf"&gt;(..)&lt;/span&gt;


&lt;span class="kr"&gt;main &lt;/span&gt;&lt;span class="nf"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;svg&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;500&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;500&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;viewBox&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;0 0 500 500&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Svg&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Attributes&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;style&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;background: #efefef&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;circle&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;cx&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;250&amp;quot;&lt;/span&gt;
            &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cy&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;250&amp;quot;&lt;/span&gt;
            &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;r&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;10&amp;quot;&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/7e3dd8b0b64b8eb99f8e18105f3cf8a892e3c59c"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This displays a 500px by 500px light gray rectangle, with a 10px circle right
in the center:&lt;/p&gt;
&lt;p&gt;&lt;img alt="the pong ball" src="//mathieu.agopian.info/blog/images/elm-pong_ball.png"&gt;&lt;/p&gt;
&lt;p&gt;Let's pull this circle out and make a &lt;code&gt;viewBall&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         , viewBox &amp;quot;0 0 500 500&amp;quot;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ circle&lt;/span&gt;
&lt;span class="gd"&gt;-            [ cx &amp;quot;250&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            , cy &amp;quot;250&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            , r &amp;quot;10&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            ]&lt;/span&gt;
&lt;span class="gd"&gt;-            []&lt;/span&gt;
&lt;span class="gi"&gt;+        [ viewBall&lt;/span&gt;
         ]
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall : Svg msg&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall =&lt;/span&gt;
&lt;span class="gi"&gt;+    circle&lt;/span&gt;
&lt;span class="gi"&gt;+        [ cx &amp;quot;250&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , cy &amp;quot;250&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , r &amp;quot;10&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;span class="gi"&gt;+        []&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/e4884af17d60089d38af056394e6919e8e865052"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This doesn't give us much, yet. Now what's our next tiny step? Well, to display
a ball, the function only needs its coordinates:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         , viewBox &amp;quot;0 0 500 500&amp;quot;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ viewBall&lt;/span&gt;
&lt;span class="gi"&gt;+        [ viewBall 250 250&lt;/span&gt;
         ]


&lt;span class="gd"&gt;-viewBall : Svg msg&lt;/span&gt;
&lt;span class="gd"&gt;-viewBall =&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall : Int -&amp;gt; Int -&amp;gt; Svg.Svg msg&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall x y =&lt;/span&gt;
     circle
&lt;span class="gd"&gt;-        [ cx &amp;quot;250&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-        , cy &amp;quot;250&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        [ cx &amp;lt;| String.fromInt x&lt;/span&gt;
&lt;span class="gi"&gt;+        , cy &amp;lt;| String.fromInt y&lt;/span&gt;
         , r &amp;quot;10&amp;quot;
         ]
         []
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/7146521653c54b875d1d268cc9cbef6f71b3af16"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here we started using integers for the positions (a number of pixels), instead
of strings. This is because we will need to do some mathematics on the
position, to get the ball moving.
So we'll pass integers around, store them in our state, and at the last moment
we'll translate them back to strings.&lt;/p&gt;
&lt;p&gt;Talking about state, let's have a &lt;code&gt;Ball&lt;/code&gt; type alias for a record holding the
position:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; import Svg.Attributes exposing (..)


&lt;span class="gi"&gt;+type alias Ball =&lt;/span&gt;
&lt;span class="gi"&gt;+    { x : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    , y : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+ball =&lt;/span&gt;
&lt;span class="gi"&gt;+    { x = 250&lt;/span&gt;
&lt;span class="gi"&gt;+    , y = 250&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 main =
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
         , viewBox &amp;quot;0 0 500 500&amp;quot;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ viewBall 250 250&lt;/span&gt;
&lt;span class="gi"&gt;+        [ viewBall ball&lt;/span&gt;
         ]


&lt;span class="gd"&gt;-viewBall : Int -&amp;gt; Int -&amp;gt; Svg.Svg msg&lt;/span&gt;
&lt;span class="gd"&gt;-viewBall x y =&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall : Ball -&amp;gt; Svg.Svg msg&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall { x, y } =&lt;/span&gt;
     circle
         [ cx &amp;lt;| String.fromInt x
         , cy &amp;lt;| String.fromInt y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/42971617722d016d7f3e944ed44028eb626c5915"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/1-display-ball"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Move the ball&lt;/h2&gt;
&lt;p&gt;Well, the ball isn't worth much if it's not moving, so let's do that.
But let's pause for a moment, how would we achieve that?&lt;/p&gt;
&lt;p&gt;In javascript we'd probably use &lt;code&gt;setInterval&lt;/code&gt;, or &lt;code&gt;setTimeout&lt;/code&gt;, and move the
ball by a small amount every 16ms or so (which would give us 60fps: 1000ms /
16ms = 62.5 frames per second).
Or we could use
&lt;a href="https://developer.mozilla.org/docs/Web/API/Window/requestAnimationFrame"&gt;requestAnimationFrame&lt;/a&gt;
which is an even better solution.&lt;/p&gt;
&lt;p&gt;In elm we have the
&lt;a href="https://package.elm-lang.org/packages/elm/browser/latest/Browser-Events#onAnimationFrameDelta"&gt;onAnimationFrameDelta&lt;/a&gt;
event that we can subscribe to, which gives us the number of milliseconds
elapsed since the previous animation frame. This way we can&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;animate the ball as smoothly as possible&lt;/li&gt;
&lt;li&gt;move the ball by the proper amount, computed with the elapsed time between
two frames&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To subscribe to browser events we first need to change our program to be
embedded as a
&lt;a href="https://package.elm-lang.org/packages/elm/browser/latest/Browser#element"&gt;Browser.element&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's do that a tiny step at a time. First let's extract the &lt;code&gt;svg&lt;/code&gt; into its own view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; main =
&lt;span class="gi"&gt;+    view ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+view : Ball -&amp;gt; Svg.Svg ()&lt;/span&gt;
&lt;span class="gi"&gt;+view ball_ =&lt;/span&gt;
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
         , viewBox &amp;quot;0 0 500 500&amp;quot;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ viewBall ball&lt;/span&gt;
&lt;span class="gi"&gt;+        [ viewBall ball_&lt;/span&gt;
         ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/f0a30bb99d11882fc8cca3e0f5dd068bd373b8bb"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now let's actually change the &lt;code&gt;main&lt;/code&gt; to be a &lt;code&gt;Browser.element&lt;/code&gt;. At the top of the file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; module Main exposing (main)

&lt;span class="gi"&gt;+import Browser&lt;/span&gt;
 import Svg exposing (..)
 import Svg.Attributes exposing (..)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     }


&lt;span class="gi"&gt;+main : Program () () ()&lt;/span&gt;
 main =
&lt;span class="gd"&gt;-    view ball&lt;/span&gt;
&lt;span class="gi"&gt;+    Browser.element&lt;/span&gt;
&lt;span class="gi"&gt;+        { init = \_ -&amp;gt; ( (), Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+        , view = \_ -&amp;gt; view ball&lt;/span&gt;
&lt;span class="gi"&gt;+        , update = \_ _ -&amp;gt; ( (), Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+        , subscriptions = \_ -&amp;gt; Sub.none&lt;/span&gt;
&lt;span class="gi"&gt;+        }&lt;/span&gt;


 view : Ball -&amp;gt; Svg.Svg ()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/c31bde2ad50b520f9ea9a97a0dacd56e2ddadf84"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Well, that's an awful lot of empty parens &lt;code&gt;()&lt;/code&gt;. Those are of the &lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Basics#Never"&gt;unit
type&lt;/a&gt; in
elm. They are just placeholders for now, as we're going to need some model,
flags, messages...&lt;/p&gt;
&lt;p&gt;Also, what's with all those &lt;code&gt;\_ -&amp;gt;&lt;/code&gt;? That's how we write &lt;a href="https://elm-lang.org/docs/syntax#functions"&gt;anonymous
functions&lt;/a&gt;. And the &lt;code&gt;_&lt;/code&gt; simply
means we don't care about that argument's value.&lt;/p&gt;
&lt;p&gt;So the anonymous function provided for the element's update &lt;code&gt;\_ _ -&amp;gt; ( (), Cmd.none )&lt;/code&gt;
can be written:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;someFunction&lt;/span&gt; &lt;span class="nv"&gt;someArg&lt;/span&gt; &lt;span class="nv"&gt;someOtherArg&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kt"&gt;Cmd&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;none&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's start with a &lt;code&gt;Model&lt;/code&gt;: this is, by convention in elm, the name of the
state, the place were we store all the data needed by our system: the ball
position, the paddles, the score, you name it. For now the only piece of data
we have is the ball position, so our model can simply be a type alias to it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Model&lt;/code&gt; goes near the top of the file by convention:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; import Svg.Attributes exposing (..)


&lt;span class="gi"&gt;+type alias Model =&lt;/span&gt;
&lt;span class="gi"&gt;+    Ball&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 type alias Ball =
     { x : Int
     , y : Int
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     }


&lt;span class="gd"&gt;-main : Program () () ()&lt;/span&gt;
&lt;span class="gi"&gt;+main : Program () Model ()&lt;/span&gt;
 main =
     Browser.element
&lt;span class="gd"&gt;-        { init = \_ -&amp;gt; ( (), Cmd.none )&lt;/span&gt;
&lt;span class="gd"&gt;-        , view = \_ -&amp;gt; view ball&lt;/span&gt;
&lt;span class="gd"&gt;-        , update = \_ _ -&amp;gt; ( (), Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+        { init = \_ -&amp;gt; ( ball, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+        , view = view&lt;/span&gt;
&lt;span class="gi"&gt;+        , update = \_ model -&amp;gt; ( model, Cmd.none )&lt;/span&gt;
         , subscriptions = \_ -&amp;gt; Sub.none
         }


&lt;span class="gd"&gt;-view : Ball -&amp;gt; Svg.Svg ()&lt;/span&gt;
&lt;span class="gd"&gt;-view ball_ =&lt;/span&gt;
&lt;span class="gi"&gt;+view : Model -&amp;gt; Svg.Svg ()&lt;/span&gt;
&lt;span class="gi"&gt;+view model =&lt;/span&gt;
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
         , viewBox &amp;quot;0 0 500 500&amp;quot;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ viewBall ball_&lt;/span&gt;
&lt;span class="gi"&gt;+        [ viewBall model&lt;/span&gt;
         ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/ab18f793ba18f2abd264c67b5b5834705c29e427"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let's have a proper initialization function now: given some flags (none in our
case, so we'll just keep the unit for now), it generates the initial model and
commands (and we'll use
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Platform-Cmd#none"&gt;Cmd.none&lt;/a&gt;
for now):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     }


&lt;span class="gd"&gt;-ball =&lt;/span&gt;
&lt;span class="gd"&gt;-    { x = 250&lt;/span&gt;
&lt;span class="gd"&gt;-    , y = 250&lt;/span&gt;
&lt;span class="gd"&gt;-    }&lt;/span&gt;
&lt;span class="gi"&gt;+init : () -&amp;gt; ( Model, Cmd () )&lt;/span&gt;
&lt;span class="gi"&gt;+init _ =&lt;/span&gt;
&lt;span class="gi"&gt;+    ( { x = 250&lt;/span&gt;
&lt;span class="gi"&gt;+      , y = 250&lt;/span&gt;
&lt;span class="gi"&gt;+      }&lt;/span&gt;
&lt;span class="gi"&gt;+    , Cmd.none&lt;/span&gt;
&lt;span class="gi"&gt;+    )&lt;/span&gt;


 main : Program () Model ()
 main =
     Browser.element
&lt;span class="gd"&gt;-        { init = \_ -&amp;gt; ( ball, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+        { init = init&lt;/span&gt;
         , view = view
         , update = \_ model -&amp;gt; ( model, Cmd.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/73bb4c464eddd603ebe53cdde4b092a9f594131e"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ok, we should be mostly set up to receive events from the browser, and in
particular the animation frames. A few missing pieces: we need to
&lt;a href="https://package.elm-lang.org/packages/elm/core/latest/Platform-Sub"&gt;subscribe&lt;/a&gt;
to those events, and we need to provide a message to the subscription, which
will act as a kind of callback. The elm runtime will call our future update
function with this message and our current model, to allow us to update the
model. This updated model will then be passed down the &lt;code&gt;view&lt;/code&gt; function to
update what we see on the screen.&lt;/p&gt;
&lt;p&gt;Let's define a &lt;code&gt;Msg&lt;/code&gt; type (this is the conventional name used in elm):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     }


&lt;span class="gi"&gt;+type Msg&lt;/span&gt;
&lt;span class="gi"&gt;+    = OnAnimationFrame Float&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 init : () -&amp;gt; ( Model, Cmd () )
 init _ =
     ( { x = 250
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/8c560e9a5d9b0b0803cecedea4bd5b07b0336166"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a custom type named &lt;code&gt;OnAnimationFrame&lt;/code&gt; which takes (includes?
encapsulates? wraps? boxes?) a float which is the number of milliseconds since
the previous animation frame.&lt;/p&gt;
&lt;p&gt;We can now use this &lt;code&gt;Msg&lt;/code&gt; type everywhere we used the unit previously...&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;init&lt;/code&gt; type definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     = OnAnimationFrame Float


&lt;span class="gd"&gt;-init : () -&amp;gt; ( Model, Cmd () )&lt;/span&gt;
&lt;span class="gi"&gt;+init : () -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 init _ =
     ( { x = 250
       , y = 250
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the &lt;code&gt;main&lt;/code&gt; type definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;     )


&lt;span class="gd"&gt;-main : Program () Model ()&lt;/span&gt;
&lt;span class="gi"&gt;+main : Program () Model Msg&lt;/span&gt;
 main =
     Browser.element
         { init = init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the &lt;code&gt;view&lt;/code&gt; type definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         }


&lt;span class="gd"&gt;-view : Model -&amp;gt; Svg.Svg ()&lt;/span&gt;
&lt;span class="gi"&gt;+view : Model -&amp;gt; Svg.Svg Msg&lt;/span&gt;
 view model =
     svg
         [ width &amp;quot;500&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And in the &lt;code&gt;viewBall&lt;/code&gt; type definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         ]


&lt;span class="gd"&gt;-viewBall : Ball -&amp;gt; Svg.Svg msg&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall : Ball -&amp;gt; Svg.Svg Msg&lt;/span&gt;
 viewBall { x, y } =
     circle
         [ cx &amp;lt;| String.fromInt x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/a5a6c38d8d263c02b171efe517fcf3c85477d71b"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We can now subscribe to the event.&lt;/p&gt;
&lt;p&gt;First add the &lt;code&gt;Browser.Events&lt;/code&gt; import at the top of the file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; module Main exposing (main)

 import Browser
&lt;span class="gi"&gt;+import Browser.Events&lt;/span&gt;
 import Svg exposing (..)
 import Svg.Attributes exposing (..)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then use a &lt;code&gt;subscriptions&lt;/code&gt; function in the &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         { init = init
         , view = view
         , update = \_ model -&amp;gt; ( model, Cmd.none )
&lt;span class="gd"&gt;-        , subscriptions = \_ -&amp;gt; Sub.none&lt;/span&gt;
&lt;span class="gi"&gt;+        , subscriptions = subscriptions&lt;/span&gt;
         }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then add the &lt;code&gt;subscriptions&lt;/code&gt; function at the bottom of the file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         , r &amp;quot;10&amp;quot;
         ]
         []
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+subscriptions : Model -&amp;gt; Sub Msg&lt;/span&gt;
&lt;span class="gi"&gt;+subscriptions _ =&lt;/span&gt;
&lt;span class="gi"&gt;+    Browser.Events.onAnimationFrameDelta OnAnimationFrame&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/c6f34bebdb5630c2ff8dfc32aef50bef67baeb51"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you compiled your elm files with the &lt;code&gt;--debug&lt;/code&gt; option for &lt;a href="https://guide.elm-lang.org/install.html#elm-make"&gt;elm
make&lt;/a&gt;, you should see many
many messages being received in the time traveller debug window:&lt;/p&gt;
&lt;p&gt;&lt;img alt="OnAnimationFrame messages being listed in the time travel debugger" src="//mathieu.agopian.info/blog/images/elm-pong_debug_events.png"&gt;&lt;/p&gt;
&lt;p&gt;Let's finally add a (mostly empty) &lt;code&gt;update&lt;/code&gt; function, and add a &lt;code&gt;Flags&lt;/code&gt; type
alias on the unit type to clean things up a bit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type Msg
     = OnAnimationFrame Float


&lt;span class="gd"&gt;-init : () -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
&lt;span class="gi"&gt;+type alias Flags =&lt;/span&gt;
&lt;span class="gi"&gt;+    ()&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+init : Flags -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 init _ =
     ( { x = 250
       , y = 250
       }
     , Cmd.none
     )


&lt;span class="gd"&gt;-main : Program () Model Msg&lt;/span&gt;
&lt;span class="gi"&gt;+main : Program Flags Model Msg&lt;/span&gt;
 main =
     Browser.element
         { init = init
         , view = view
&lt;span class="gd"&gt;-        , update = \_ model -&amp;gt; ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+        , update = update&lt;/span&gt;
         , subscriptions = subscriptions
         }


&lt;span class="gi"&gt;+update : Msg -&amp;gt; Model -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
&lt;span class="gi"&gt;+update msg model =&lt;/span&gt;
&lt;span class="gi"&gt;+    ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 view : Model -&amp;gt; Svg.Svg Msg
 view model =
     svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/4e899ec200c65a9b4467ac21e2e753ee1e173b35"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/2-move-ball-maybe"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can be proud of ourselves: we changed a lot of code, but nothing changed
visually: the ball still isn't moving! Promise, we're moving this ball next ;)&lt;/p&gt;
&lt;h2&gt;Move the ball (for real)&lt;/h2&gt;
&lt;p&gt;Now that we have everything in place, moving the ball is simply a matter of
changing the &lt;code&gt;x&lt;/code&gt; or &lt;code&gt;y&lt;/code&gt; coordinates.
And where better to do that than on each animation frame? We already have a
subscription that fires an "event" (a message in elm's vocabulary). We also
have an &lt;code&gt;update&lt;/code&gt; function that is called with those messages, allowing us to
return a new (and updated) model.&lt;/p&gt;
&lt;p&gt;Yes, we need to return a new model because in elm everything is immutable: you
don't change a model, you don't mutate it, you simply create a new one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; update : Msg -&amp;gt; Model -&amp;gt; ( Model, Cmd Msg )
 update msg model =
&lt;span class="gd"&gt;-    ( model, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+    case msg of&lt;/span&gt;
&lt;span class="gi"&gt;+        OnAnimationFrame timeDelta -&amp;gt;&lt;/span&gt;
&lt;span class="gi"&gt;+            ( { model | x = model.x + 4 }, Cmd.none )&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/065042448a7f1acedb9a66f730b8c51040c2392c"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/3-move-ball"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Instead of always returning the same model we were passed in the update
function, we now add 4 pixels to the &lt;code&gt;x&lt;/code&gt; coordinates of the ball.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;{ foo | bar = crux }&lt;/code&gt; notation is syntactic sugar to create a new record
from the content of the &lt;code&gt;foo&lt;/code&gt; record, but with the &lt;code&gt;bar&lt;/code&gt; field set to the new
&lt;code&gt;crux&lt;/code&gt; value.&lt;/p&gt;
&lt;p&gt;Once compiled and the browser tab refreshed, you should see the mighty ball
moving rather quickly towards the right (and then disappearing!).&lt;/p&gt;
&lt;p&gt;Now that the ball is moving, let's set us up quickly with some better tooling.&lt;/p&gt;
&lt;h2&gt;Tooling&lt;/h2&gt;
&lt;p&gt;At this point, you should start getting tired of running &lt;code&gt;elm make&lt;/code&gt;, then
switching to the browser, refreshing the page... modifying the code, and then
starting over.
Those few steps can quickly become tedious, and that's why most elm developers
take advantage of:&lt;/p&gt;
&lt;h3&gt;Live reloading&lt;/h3&gt;
&lt;p&gt;Live reloading is automatically re-compiling the code whenever a file changes,
and then automatically refreshing the browser tab.
There are a few tools available that I know of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wking-io/elm-live"&gt;elm-live&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/halfzebra/create-elm-app"&gt;create-elm-app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://parceljs.org/elm.html"&gt;parceljs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They all offer some kind of web server that injects some javascript in the page, and then whenever a file changes, recompile the project, and communicate with the loaded web page using a websocket so it reloads.
I've used all three in various projects, and they all have their pros and cons.
I find elm-live to be one of the smallest and simplest to use, but really do
feel free to pick one (but do pick one, it's really worth it ;)&lt;/p&gt;
&lt;h3&gt;Auto code formatting&lt;/h3&gt;
&lt;p&gt;Another tedious task is indenting and formatting the code properly. In elm
indentation matters, and as in any written code, readability is important.  The
elm community has very broadly adopted a common code formatting tool that gives
us a way to all share a same code style, which is a real blessing.&lt;/p&gt;
&lt;p&gt;It also appears that once you stop caring about a code style, and let the
machine do it for you, it really frees your mind from this chore, and removes
all the bikeshedding and useless discussions and trolls.&lt;/p&gt;
&lt;p&gt;This code formatting tool is &lt;a href="https://github.com/avh4/elm-format"&gt;elm-format&lt;/a&gt;,
and can be configured to automatically reformat the code on save in most
editors.&lt;/p&gt;
&lt;p&gt;With those two tools installed and set up, let us continue with our game:
adding a paddle!&lt;/p&gt;
&lt;h2&gt;Adding a paddle&lt;/h2&gt;
&lt;p&gt;The right paddle will be a simple rectangle (for now at least), 10 pixels wide,
50 pixels high, 10 pixels from the right border, and will start at the middle.&lt;/p&gt;
&lt;p&gt;This makes the starting paddle coordinates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;x: 500 - 10 (paddle width) - 10 (margin with the right border) = 480&lt;/li&gt;
&lt;li&gt;y: 500 / 2 (so it is centered vertically) - 50 / 2 (center of the paddle) = 225&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
         [ viewBall model
&lt;span class="gi"&gt;+        , rect&lt;/span&gt;
&lt;span class="gi"&gt;+            [ x &amp;quot;480&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+            , y &amp;quot;225&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+            , width &amp;quot;10&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+            , height &amp;quot;50&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+            ]&lt;/span&gt;
&lt;span class="gi"&gt;+            []&lt;/span&gt;
         ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/473e5ebe5c282632c16269f0941d2afa8ff3f2b7"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Behold the mighty paddle!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Right paddle with the ball moving towards it" src="//mathieu.agopian.info/blog/images/elm-pong_right_paddle.png"&gt;&lt;/p&gt;
&lt;p&gt;One problem though... the ball isn't boucing off of it. What good is a paddle
that doesn't bounce?&lt;/p&gt;
&lt;p&gt;But first, what does "bouncing" mean? In our case, bouncing of a vertical
paddle means changing the horizontal direction of the ball. But we don't have a
proper "direction" yet. For now, we only need a horizontal "direction" (or
speed, or vector):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type alias Ball =
     { x : Int
     , y : Int
&lt;span class="gi"&gt;+    , horizSpeed : Int&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -28,6 +29,7 @@ init : Flags -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 init _ =
     ( { x = 250
       , y = 250
&lt;span class="gi"&gt;+      , horizSpeed = 4&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -47,7 +49,7 @@ update : Msg -&amp;gt; Model -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 update msg model =
     case msg of
         OnAnimationFrame timeDelta -&amp;gt;
&lt;span class="gd"&gt;-            ( { model | x = model.x + 4 }, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+            ( { model | x = model.x + model.horizSpeed }, Cmd.none )&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/a255c8ca93ee898ab272eb820ade1d34abed7fac"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In case you're wondering what those &lt;code&gt;@@ -28,6 +29,7 @@ init : Flags -&amp;gt; ( Model,
Cmd Msg )&lt;/code&gt; lines mean: that's the diff &lt;a href="https://en.wikipedia.org/wiki/Diff#Unified_format"&gt;unified
format&lt;/a&gt; telling us where the
line is located: it's line 28, and it gives us a bit of context: it's in the
&lt;code&gt;init&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;What we did here was to extract the number of pixels we were adding to the &lt;code&gt;x&lt;/code&gt;
position of the ball into a &lt;code&gt;horizSpeed&lt;/code&gt; state in the model.&lt;/p&gt;
&lt;p&gt;Now that we have a horizontal speed, boucing (horizontally) simply means
"reversing" its value, which is achieved by changing its sign. If we were
adding 4 pixels per frame and bounced of the right paddle, we'd now need to
substract 4 pixels per frame to the &lt;code&gt;x&lt;/code&gt; coordinate.&lt;/p&gt;
&lt;p&gt;Before going further, let's do some cleanup and slight reorganization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extract the ball radius and store it in the state:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type alias Ball =
     { x : Int
     , y : Int
&lt;span class="gi"&gt;+    , radius : Int&lt;/span&gt;
     , horizSpeed : Int
     }

&lt;span class="gu"&gt;@@ -29,6 +30,7 @@ init : Flags -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 init _ =
     ( { x = 250
       , y = 250
&lt;span class="gi"&gt;+      , radius = 10&lt;/span&gt;
       , horizSpeed = 4
       }
     , Cmd.none
&lt;span class="gu"&gt;@@ -72,11 +74,11 @@ view model =&lt;/span&gt;


 viewBall : Ball -&amp;gt; Svg.Svg Msg
&lt;span class="gd"&gt;-viewBall { x, y } =&lt;/span&gt;
&lt;span class="gi"&gt;+viewBall { x, y, radius } =&lt;/span&gt;
     circle
         [ cx &amp;lt;| String.fromInt x
         , cy &amp;lt;| String.fromInt y
&lt;span class="gd"&gt;-        , r &amp;quot;10&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+        , r &amp;lt;| String.fromInt radius&lt;/span&gt;
         ]
         []
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/51867fe6ffd7d4109e32ab27e0ec92b1d4bfee4a"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change the state to be a record holding a ball (instead of BEING a ball)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type alias Model =
&lt;span class="gd"&gt;-    Ball&lt;/span&gt;
&lt;span class="gi"&gt;+    { ball : Ball&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;


 type alias Ball =
&lt;span class="gu"&gt;@@ -28,15 +29,22 @@ type alias Flags =&lt;/span&gt;

 init : Flags -&amp;gt; ( Model, Cmd Msg )
 init _ =
&lt;span class="gd"&gt;-    ( { x = 250&lt;/span&gt;
&lt;span class="gd"&gt;-      , y = 250&lt;/span&gt;
&lt;span class="gd"&gt;-      , radius = 10&lt;/span&gt;
&lt;span class="gd"&gt;-      , horizSpeed = 4&lt;/span&gt;
&lt;span class="gi"&gt;+    ( { ball =&lt;/span&gt;
&lt;span class="gi"&gt;+            initBall&lt;/span&gt;
       }
     , Cmd.none
     )


&lt;span class="gi"&gt;+initBall : Ball&lt;/span&gt;
&lt;span class="gi"&gt;+initBall =&lt;/span&gt;
&lt;span class="gi"&gt;+    { x = 250&lt;/span&gt;
&lt;span class="gi"&gt;+    , y = 250&lt;/span&gt;
&lt;span class="gi"&gt;+    , radius = 10&lt;/span&gt;
&lt;span class="gi"&gt;+    , horizSpeed = 4&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 main : Program Flags Model Msg
 main =
     Browser.element
&lt;span class="gu"&gt;@@ -51,18 +59,22 @@ update : Msg -&amp;gt; Model -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 update msg model =
     case msg of
         OnAnimationFrame timeDelta -&amp;gt;
&lt;span class="gd"&gt;-            ( { model | x = model.x + model.horizSpeed }, Cmd.none )&lt;/span&gt;
&lt;span class="gi"&gt;+            let&lt;/span&gt;
&lt;span class="gi"&gt;+                ball =&lt;/span&gt;
&lt;span class="gi"&gt;+                    model.ball&lt;/span&gt;
&lt;span class="gi"&gt;+            in&lt;/span&gt;
&lt;span class="gi"&gt;+            ( { model | ball = { ball | x = ball.x + ball.horizSpeed } }, Cmd.none )&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;span class="gd"&gt;-view model =&lt;/span&gt;
&lt;span class="gi"&gt;+view { ball } =&lt;/span&gt;
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
         , viewBox &amp;quot;0 0 500 500&amp;quot;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
&lt;span class="gd"&gt;-        [ viewBall model&lt;/span&gt;
&lt;span class="gi"&gt;+        [ viewBall ball&lt;/span&gt;
         , rect
             [ x &amp;quot;480&amp;quot;
             , y &amp;quot;225&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/3ad3edeb268a9beb1633e2b4efff4a487ca12899"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extract the right paddle into its own type and state&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; type alias Model =
     { ball : Ball
&lt;span class="gi"&gt;+    , paddle : Paddle&lt;/span&gt;
     }


&lt;span class="gu"&gt;@@ -19,6 +20,14 @@ type alias Ball =&lt;/span&gt;
     }


&lt;span class="gi"&gt;+type alias Paddle =&lt;/span&gt;
&lt;span class="gi"&gt;+    { x : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    , y : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    , width : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    , height : Int&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 type Msg
     = OnAnimationFrame Float

&lt;span class="gu"&gt;@@ -31,6 +40,7 @@ init : Flags -&amp;gt; ( Model, Cmd Msg )&lt;/span&gt;
 init _ =
     ( { ball =
             initBall
&lt;span class="gi"&gt;+      , paddle = initPaddle&lt;/span&gt;
       }
     , Cmd.none
     )
&lt;span class="gu"&gt;@@ -45,6 +55,15 @@ initBall =&lt;/span&gt;
     }


&lt;span class="gi"&gt;+initPaddle : Paddle&lt;/span&gt;
&lt;span class="gi"&gt;+initPaddle =&lt;/span&gt;
&lt;span class="gi"&gt;+    { x = 480&lt;/span&gt;
&lt;span class="gi"&gt;+    , y = 225&lt;/span&gt;
&lt;span class="gi"&gt;+    , width = 10&lt;/span&gt;
&lt;span class="gi"&gt;+    , height = 50&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 main : Program Flags Model Msg
 main =
     Browser.element
&lt;span class="gu"&gt;@@ -70,7 +89,7 @@ update msg model =&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;span class="gd"&gt;-view { ball } =&lt;/span&gt;
&lt;span class="gi"&gt;+view { ball, paddle } =&lt;/span&gt;
     svg
         [ width &amp;quot;500&amp;quot;
         , height &amp;quot;500&amp;quot;
&lt;span class="gu"&gt;@@ -78,13 +97,7 @@ view { ball } =&lt;/span&gt;
         , Svg.Attributes.style &amp;quot;background: #efefef&amp;quot;
         ]
         [ viewBall ball
&lt;span class="gd"&gt;-        , rect&lt;/span&gt;
&lt;span class="gd"&gt;-            [ x &amp;quot;480&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            , y &amp;quot;225&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            , width &amp;quot;10&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            , height &amp;quot;50&amp;quot;&lt;/span&gt;
&lt;span class="gd"&gt;-            ]&lt;/span&gt;
&lt;span class="gd"&gt;-            []&lt;/span&gt;
&lt;span class="gi"&gt;+        , viewPaddle paddle&lt;/span&gt;
         ]


&lt;span class="gu"&gt;@@ -98,6 +111,17 @@ viewBall { x, y, radius } =&lt;/span&gt;
         []


&lt;span class="gi"&gt;+viewPaddle : Paddle -&amp;gt; Svg.Svg Msg&lt;/span&gt;
&lt;span class="gi"&gt;+viewPaddle paddle =&lt;/span&gt;
&lt;span class="gi"&gt;+    rect&lt;/span&gt;
&lt;span class="gi"&gt;+        [ x &amp;lt;| String.fromInt paddle.x&lt;/span&gt;
&lt;span class="gi"&gt;+        , y &amp;lt;| String.fromInt paddle.y&lt;/span&gt;
&lt;span class="gi"&gt;+        , width &amp;lt;| String.fromInt paddle.width&lt;/span&gt;
&lt;span class="gi"&gt;+        , height &amp;lt;| String.fromInt paddle.height&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;span class="gi"&gt;+        []&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 subscriptions : Model -&amp;gt; Sub Msg
 subscriptions _ =
     Browser.Events.onAnimationFrameDelta OnAnimationFrame
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/29841ff6fea5b3d5a0eb6e960700acbc31baa936"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We're now ready to detect the bounce, and reverse the direction the ball is
moving.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/4-display-paddle"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Bouncing the ball off the paddle&lt;/h2&gt;
&lt;p&gt;The ball should bounce off the paddle, which is when the ball "touches" the
paddle. That means that the ball should bounce (reverse direction) when both
those conditions are met:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;x&lt;/code&gt; position of the ball (center) is more than &lt;code&gt;radius&lt;/code&gt; away from the &lt;code&gt;x&lt;/code&gt;
  position of the paddle&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;y&lt;/code&gt; position of the ball is between the top (the paddle's &lt;code&gt;y&lt;/code&gt;) and the
  bottom (the paddle's &lt;code&gt;y&lt;/code&gt; plus the paddle's height) of the paddle&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's make a helper function for that, which given a ball and a paddle will
return &lt;code&gt;True&lt;/code&gt; if the ball should bounce, and display the result of that check
in each animation frame in the console:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                 ball =
                     model.ball

&lt;span class="gi"&gt;+                shouldBounce =&lt;/span&gt;
&lt;span class="gi"&gt;+                    shouldBallBounce model.paddle model.ball&lt;/span&gt;
&lt;span class="gi"&gt;+                        |&amp;gt; Debug.log &amp;quot;shouldBounce&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
                 updatedBall =
                     { ball | x = ball.x + ball.horizSpeed }
             in
             ( { model | ball = updatedBall }, Cmd.none )


&lt;span class="gi"&gt;+shouldBallBounce : Paddle -&amp;gt; Ball -&amp;gt; Bool&lt;/span&gt;
&lt;span class="gi"&gt;+shouldBallBounce paddle ball =&lt;/span&gt;
&lt;span class="gi"&gt;+    (ball.x + ball.radius &amp;gt;= paddle.x)&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y - 50 // 2)&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50 // 2)&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
 view : Model -&amp;gt; Svg.Svg Msg
 view { ball, paddle } =
     svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/8ab7760be55cc34cfc4023e399b579e6f1112f89"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When running the code, you should see &lt;code&gt;shouldBounce: False&lt;/code&gt; displayed several
times per second until the ball reaches the right paddle, and the message then
displaying &lt;code&gt;shouldBounce: True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It works! We can now use this to update the ball's horizontal speed (its
direction) according to the check:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;                 shouldBounce =
                     shouldBallBounce model.paddle model.ball
&lt;span class="gd"&gt;-                        |&amp;gt; Debug.log &amp;quot;shouldBounce&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                horizSpeed =&lt;/span&gt;
&lt;span class="gi"&gt;+                    if shouldBounce then&lt;/span&gt;
&lt;span class="gi"&gt;+                        ball.horizSpeed * -1&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+                    else&lt;/span&gt;
&lt;span class="gi"&gt;+                        ball.horizSpeed&lt;/span&gt;

                 updatedBall =
&lt;span class="gd"&gt;-                    { ball | x = ball.x + ball.horizSpeed }&lt;/span&gt;
&lt;span class="gi"&gt;+                    { ball&lt;/span&gt;
&lt;span class="gi"&gt;+                        | x = ball.x + horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                        , horizSpeed = horizSpeed&lt;/span&gt;
&lt;span class="gi"&gt;+                    }&lt;/span&gt;
             in
             ( { model | ball = updatedBall }, Cmd.none )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/31d86df31b40ccf2ec1d811266246d894de410ca"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Such joy, a mighty ball boucing off a glorious paddle! We're so good! The world
is ours! This feeling is why I became a developer in the first place. Feeling
invincible, powerful, knowledgeable.&lt;/p&gt;
&lt;h2&gt;The horror&lt;/h2&gt;
&lt;p&gt;And at the same time, there's a nagging feeling in the back of my head: how can
I make sure that the code is working properly? I mean, most of it is very
straightforward, not much complexity. But maybe the &lt;code&gt;shouldBallBounce&lt;/code&gt; which
seems a bit cryptic.&lt;/p&gt;
&lt;p&gt;To make the doubt go away, let's try a few different starting positions for the
ball: if it starts at 10, everything seems fine (it doesn't bounce and
disappears off screen as expected).
If it starts at 400, same thing.
What about 200? OH NOES!!!!!!11!1!! It bounces back, even though it's very
clearly way above the paddle!
Wait, what about 260? OH NOES!!!!!!11!1!! It goes right through the paddle!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; initBall : Ball
 initBall =
     { x = 250
&lt;span class="gd"&gt;-    , y = 250&lt;/span&gt;
&lt;span class="gi"&gt;+    , y = 260&lt;/span&gt;
     , radius = 10
     , horizSpeed = 4
     }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="the horror" src="//mathieu.agopian.info/blog/images/elm-pong_the_horror.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/69f0b57f95a4af30579cb3a3cc99c88993e18d4d"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ok, I'm so bad. I'm worthless. I know nothing about programming. I should stop
and switch jobs. Maybe become a shepherd or something.&lt;/p&gt;
&lt;p&gt;But first, let's try to find the issue here. I'm glad we tried a few different
cases, and caught the issue early, it should be easier to deal with. By the
way, that's why people like testing their code with corner cases (either using
automated tests, or manual ones).&lt;/p&gt;
&lt;p&gt;Let's look at the &lt;code&gt;shouldBallBounce&lt;/code&gt; code again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;shouldBallBounce&lt;/span&gt; &lt;span class="nf"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Paddle&lt;/span&gt; &lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Ball&lt;/span&gt; &lt;span class="nf"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
&lt;span class="nv"&gt;shouldBallBounce&lt;/span&gt; &lt;span class="nv"&gt;paddle&lt;/span&gt; &lt;span class="nv"&gt;ball&lt;/span&gt; &lt;span class="nf"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ball&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="nf"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;ball&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;radius&lt;/span&gt; &lt;span class="nf"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nv"&gt;paddle&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ball&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="nf"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nv"&gt;paddle&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="nf"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="nf"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ball&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="nf"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nv"&gt;paddle&lt;/span&gt;&lt;span class="nf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt; &lt;span class="nf"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="nf"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Mmmmm... it seems we dealt with the paddle as if its &lt;code&gt;y&lt;/code&gt; position was in the
center of the paddle. But in SVG, it's the top left of the rectangle! That's
why we had to computer the center to display it vertically centered in the game
window.&lt;/p&gt;
&lt;p&gt;So the correct code should instead be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; shouldBallBounce : Paddle -&amp;gt; Ball -&amp;gt; Bool
 shouldBallBounce paddle ball =
     (ball.x + ball.radius &amp;gt;= paddle.x)
&lt;span class="gd"&gt;-        &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y - 50 // 2)&lt;/span&gt;
&lt;span class="gd"&gt;-        &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50 // 2)&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;amp;&amp;amp; (ball.y &amp;gt;= paddle.y)&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;amp;&amp;amp; (ball.y &amp;lt;= paddle.y + 50)&lt;/span&gt;


 view : Model -&amp;gt; Svg.Svg Msg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/commit/cde809af95dd1ecf069cefb55795205f025c5460"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/magopian/elm-pong/tree/5-bounce-ball"&gt;Source code up to this point&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This looks way better, if the starting &lt;code&gt;y&lt;/code&gt; position of the ball is between 225
and 275 it properly bounces.&lt;/p&gt;
&lt;p&gt;We still have the corner case of the ball's center grazing off the paddle (so
the ball's &lt;code&gt;y&lt;/code&gt; position being for example 224 or 276): the center isn't within
the paddle, but the ball's radius being 10 pixels, there's still 9 pixels of
the ball clipping through the paddle. We could decide that we instead want the
ball to bounce off, but it then looks weird because the hitbox of the ball is a
square (we check on the &lt;code&gt;x&lt;/code&gt; and the &lt;code&gt;y&lt;/code&gt;, not the shortest distance between the
ball and the paddle which could be in diagonal.&lt;/p&gt;
&lt;p&gt;Anyway, let's decide that this is good enough for now, and an improvement to
make in the future!&lt;/p&gt;
&lt;p&gt;Also, let's feel good about ourselves again, and maybe even more: we were
clever and persistent enough to investigate the bug and fix it! Maybe we can
still be working in a programming job after all.&lt;/p&gt;
&lt;p&gt;(Yes, this kind of ups and downs are very very common, at least in my case).&lt;/p&gt;
&lt;h2&gt;Now draw the rest of the owl&lt;/h2&gt;
&lt;p&gt;(For the reference to this meme, check &lt;a href="https://knowyourmeme.com/memes/how-to-draw-an-owl"&gt;How to draw an
owl&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;We've come a long way, but we're still a long way from having a finished pong
game! This post being long enough as it is, let's wrap it here.&lt;/p&gt;
&lt;p&gt;I might publish a follow up some day, please let me
know via &lt;a href="mailto:mathieu@agopian.info?subject=making a pong game in elm"&gt;email&lt;/a&gt;
or &lt;a href="https://twitter.com/magopian"&gt;twitter&lt;/a&gt; if you'd be
interested.&lt;/p&gt;
&lt;p&gt;I've tried something different with the code snippets in this blog post, using
the diff format. Did it make it easier to follow? Or worse? It does have the
main drawback of not having the elm syntax highlighting. But on the other hand
it shows exactly what changes between two code excerpts.&lt;/p&gt;
&lt;p&gt;What do you think?&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;There's now a &lt;a href="//mathieu.agopian.info/blog/making-a-pong-game-in-elm-2.html"&gt;follow up&lt;/a&gt;.&lt;/p&gt;</content><category term="elm"></category><category term="gamedev"></category></entry><entry><title>Python et asyncio : la recette du bonheur ?</title><link href="//mathieu.agopian.info/blog/python-et-asyncio-la-recette-du-bonheur.html" rel="alternate"></link><published>2016-08-09T10:57:00+02:00</published><updated>2016-08-09T10:57:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2016-08-09:/blog/python-et-asyncio-la-recette-du-bonheur.html</id><summary type="html">&lt;p&gt;&lt;cite&gt;asyncio&lt;/cite&gt; est une librairie inclue dans la &lt;cite&gt;stdlib&lt;/cite&gt; des dernières versions de
python3, et qui permet de faire de la programmation asynchrone.&lt;/p&gt;
&lt;p&gt;Oui, ok, mais ça veut dire quoi au juste&amp;nbsp;?&lt;/p&gt;
&lt;div class="section" id="asynchrone-concurrent-coroutine-parallele"&gt;
&lt;h2&gt;Asynchrone, concurrent, coroutine, parallèle&lt;/h2&gt;
&lt;p&gt;Il y a un intrus dans ce titre. Ces termes sont utilisés pour décrire des …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;cite&gt;asyncio&lt;/cite&gt; est une librairie inclue dans la &lt;cite&gt;stdlib&lt;/cite&gt; des dernières versions de
python3, et qui permet de faire de la programmation asynchrone.&lt;/p&gt;
&lt;p&gt;Oui, ok, mais ça veut dire quoi au juste&amp;nbsp;?&lt;/p&gt;
&lt;div class="section" id="asynchrone-concurrent-coroutine-parallele"&gt;
&lt;h2&gt;Asynchrone, concurrent, coroutine, parallèle&lt;/h2&gt;
&lt;p&gt;Il y a un intrus dans ce titre. Ces termes sont utilisés pour décrire des
styles de programmation, mais sont parfois mal compris, ou prêtent à
confusion&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;asynchrone == concurrent == un seul process, dans lequel plusieurs morceaux
de code, les &lt;cite&gt;coroutines&lt;/cite&gt;, s'exécutent l'une après l'autre, dans le désordre,
de manière non bloquante (on va y revenir)&lt;/li&gt;
&lt;li&gt;parallèle == multiprocessing == plusieurs process se partagent un ou
plusieurs morceaux de code, morceaux qui s'exécutent &lt;strong&gt;en même temps&lt;/strong&gt;, sur
plusieurs processeurs/cœurs/machines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L'approche asynchrone/concurrente/non bloquante&amp;nbsp;est légere tant à
l'implémentation qu'à l'execution - les &lt;cite&gt;coroutines&lt;/cite&gt; sont très peu gourmandes
en mémoire et très rapides à lancer. Elles s'exécutent au sein du même process,
ont toutes accès à la même zone mémoire et il n'est donc pas nécessaire
d'échanger des messages entre elles pour partager des informations.
Cependant, elles n'accélèrent pas le programme car il n'y a toujours qu'un seul
process. Le programme ne sera donc plus rapide que dans certains cas
particuliers. Dans d'autres, il pourra même être plus lent à cause du surcoût
de gestion impliqué par l'utilisation de &lt;cite&gt;coroutines&lt;/cite&gt;.&lt;/p&gt;
&lt;p&gt;Dans une approche parallèle, le programme s'exécute sur plusieurs process, ce
qui l'accélère mécaniquement jusqu'à potentiellement diviser sa durée
d'execution par le nombre de coeurs mobilisés. Selon les langages et les
libraries utilisées, il est par ailleurs possible de lancer un programme sur
plusieurs machines. C'est une approche beaucoup plus lourde tant à
l'implémentation qu'à l'éxécution, car il faut lancer, synchroniser et arréter
chaque processus, qui en sus demande davantage de mémoire et l'utilisation d'un
système de message pour échanger avec ses voisins.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="oui-d-accord-mais-comment-ca-marche"&gt;
&lt;h2&gt;Oui, d'accord, mais comment ça marche&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;Suivant les langages, il y a différentes façons de concevoir l'exécution
asynchrone et/ou de faire des appels non bloquants:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;en javascript&amp;nbsp;: un appel asynchrone rend la main directement et passe à la
ligne suivante. Son résultat est ensuite récupéré par le biais d'un
&lt;cite&gt;callback&lt;/cite&gt;.&lt;/li&gt;
&lt;li&gt;en clojure ou en go&amp;nbsp;: des &lt;cite&gt;canaux&lt;/cite&gt; sont utilisés pour envoyer et recevoir des
messages. Ils sont assimilables à des tapis roulants entre deux morceaux de
programmes, l'un qui pose des messages dessus quand bon lui semble, l'autre
que les récupère également quand il le peut. Les deux bouts de code sont donc
désynchronisés et ne se bloquent pas l'un l'autre.&lt;/li&gt;
&lt;li&gt;en python&amp;nbsp;: un appel asynchrone passe par l'utilisation de générateurs,
appelées &lt;cite&gt;coroutines&lt;/cite&gt; dans le cas de &lt;cite&gt;asyncio&lt;/cite&gt;. Dans ce cas, on gère
explicitement la boucle d'exécution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans le cas de python/&lt;cite&gt;asyncio&lt;/cite&gt;, c'est grâce aux mots-clés &lt;tt class="docutils literal"&gt;await&lt;/tt&gt; (ou
&lt;tt class="docutils literal"&gt;yield from&lt;/tt&gt;) qu'un bout de code signale à la boucle d'exécution qu'il a
temporairement terminé sa tâche. Cette dernière passe donc la main à d'autres
bouts de code qui ont quelque chose à faire, au lieu d'attendre
séquentiellement (de manière synchrone) que chaque bout de code se termine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="asynchrone-plus-rapide-ou-pas"&gt;
&lt;h2&gt;Asynchrone == plus rapide, ou pas...&lt;/h2&gt;
&lt;p&gt;Mais alors, si le code s'exécute toujours sur un seul et même process et que la
gestion des &lt;cite&gt;coroutines&lt;/cite&gt; implique un surcoût de temps de calcul, est-ce que mon
programme ne va être plus lent ?!&lt;/p&gt;
&lt;p&gt;Petite expérience&amp;nbsp;:&lt;/p&gt;
&lt;p&gt;En synchrone/bloquant&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import asyncio

def foo():
    pass

for i in range(10000):
    foo()
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
real    0m0.087s
user    0m0.071s
sys     0m0.013s
&lt;/pre&gt;
&lt;p&gt;En asynchrone/non bloquant&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import asyncio

async def foo():
    pass

loop = asyncio.get_event_loop()
for i in range(10000):
    loop.run_until_complete(foo())
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
real    0m0.452s
user    0m0.429s
sys     0m0.019s
&lt;/pre&gt;
&lt;p&gt;Un rapide calcul indique que la gestion des 10000 &lt;cite&gt;coroutines&lt;/cite&gt; implique un
surcoût d'environ 360ms (l'import du module &lt;cite&gt;asyncio&lt;/cite&gt;, qui se fait une seule
fois au chargement du programme, a été fait dans les deux cas afin de ne pas
fausser les mesures).&lt;/p&gt;
&lt;p&gt;Le but de cet exemple aberrant n'est pas de prouver que les &lt;cite&gt;coroutines&lt;/cite&gt; sont
lentes (elles ne le sont pas), mais que la programmation asynchrone en
elle-même ne fait pas aller n'importe quel programme plus vite (mais ça, vous
vous en doutiez).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="asyncio-plus-rapide-pour-io-bound"&gt;
&lt;h2&gt;Asyncio plus rapide pour IO-bound&lt;/h2&gt;
&lt;p&gt;Mais alors, &lt;cite&gt;asyncio&lt;/cite&gt; ne sert à rien&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Laissez-moi vous conter l'histoire de Bob, qui veut télécharger toutes les
images de chat de son site préféré.  Voici un petit morceau de son (pseudo-)
code&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
for url in urls:
    img = get_image_from_website(url)
    thumbnails = compute_thumbnails(img)
    ...
&lt;/pre&gt;
&lt;p&gt;Le programme va tour à tour télécharger les images sur le site, puis en faire
des miniatures. Il y a donc deux cas différents&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;le téléchargement de l'image depuis le site, qu'on dit &amp;quot;IO-bound&amp;quot;, car limité
(lié) par l'IO (l'input-output, entrée sortie, tout échange entre le
programme et l'extérieur). Pendant ce téléchargement, le programme va passer
la majeure partie du temps à attendre que la requête soit reçue par le
serveur distant, puis traitée, puis que les données soient envoyées, puis
reçues. Pendant tout ce temps, le programme est bloqué, et ne fait rien
d'autre. C'est un appel bloquant.&lt;/li&gt;
&lt;li&gt;le calcul de la miniature, qu'on dit&amp;nbsp;&amp;quot;CPU-bound&amp;quot;, c'est-à-dire limité (lié)
par le CPU, par la puissance de calcul de l'ordinateur, du process qui fait
tourner le programme. Aucune attente ici. Plus il y a de puissance de calcul
(plus le processeur est rapide, plus il y a de CPU disponible), plus le
programme ira vite.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L'idéal serait de pouvoir calculer la miniature d'une image pendant le temps
d'attente du téléchargement d'une autre image&amp;nbsp;! C'est une technique connue
depuis bien longtemps dans l'industrie, le &amp;quot;travail en temps masqué&amp;quot;&amp;nbsp;: pendant
qu'une machine travaille, l'employé peut faire autre chose, comme remplir le
chargeur de la machine, décharger les produits finis, lancer une autre machine,
etc...&lt;/p&gt;
&lt;p&gt;C'est la grande force de &lt;cite&gt;asyncio&lt;/cite&gt; : pouvoir faire des appels non bloquants,
c'est à dire profiter d'un temps d'attente pour pouvoir faire autre chose.&lt;/p&gt;
&lt;p&gt;Reprenons notre exemple&amp;nbsp;:&lt;/p&gt;
&lt;p&gt;En synchrone/bloquant&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import requests
from lxml import html
from PIL import Image

URL_TPL = &amp;quot;http://bonjourlechat.tumblr.com/page/{}&amp;quot;
THUMBNAIL_SIZES = ((100, 100), (200, 200), (300, 300), (400, 400), (500, 500))

def get_image_from_website(url):
    page = requests.get(url)
    # Get the html content as a tree.
    tree = html.fromstring(page.content)
    # Use xpath to get the image url.
    img_url = tree.xpath('//figure//img/&amp;#64;src')[0]
    data = requests.get(img_url, stream=True)
    data.raw.decode_content = True
    img = Image.open(data.raw)
    return img

def compute_thumbnails(img):
    thumbnails = []
    for size in THUMBNAIL_SIZES:
        thumbnails.append(img.thumbnail(size))
    return thumbnails

def get_all_thumbnails():
    for i in range(1, 11):
        img = get_image_from_website(URL_TPL.format(i))
        thumbnails = compute_thumbnails(img)

get_all_thumbnails()
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
real    0m9.722s
user    0m0.466s
sys     0m0.089s
&lt;/pre&gt;
&lt;p&gt;Soit environ 10 secondes, une seconde par image.&lt;/p&gt;
&lt;p&gt;En asynchrone/non bloquant&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import aiohttp
import asyncio
from io import BytesIO
from lxml import html
from PIL import Image

URL_TPL = &amp;quot;http://bonjourlechat.tumblr.com/page/{}&amp;quot;
THUMBNAIL_SIZES = ((100, 100), (200, 200), (300, 300), (400, 400), (500, 500))

async def get_image_from_website(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as page:
            # Get the html content as a tree.
            tree = html.fromstring(await page.text())

        # Use xpath to get the image url.
        img_url = tree.xpath('//figure//img/&amp;#64;src')[0]

        # Store the raw image data in a file-like object that Pillow can use.
        memfile = BytesIO()
        async with session.get(img_url) as data:
            memfile.write(await data.read())

    img = Image.open(memfile)
    return img

async def compute_thumbnails(img):
    thumbnails = []
    for size in THUMBNAIL_SIZES:
        thumbnails.append(await loop.run_in_executor(None, img.thumbnail, size))
    return thumbnails

async def get_thumbnail(url):
    img = await get_image_from_website(url)
    thumbnails = await compute_thumbnails(img)


tasks = [get_thumbnail(URL_TPL.format(i)) for i in range(1, 11)]
loop = asyncio.get_event_loop()
thumbnails = loop.run_until_complete(asyncio.gather(*tasks))
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
real    0m4.139s
user    0m0.795s
sys     0m0.094s
&lt;/pre&gt;
&lt;p&gt;Soit environ 4 secondes, 0.5 seconde par image.&lt;/p&gt;
&lt;p&gt;Plusieurs remarques&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;dans le cas du code asynchrone, il faut faire bien attention d'utiliser
uniquement des appels non bloquants. On utilise donc &lt;cite&gt;aiohttp&lt;/cite&gt; pour récupérer
la page et l'image, puis faire les miniatures (en utilisant
&lt;tt class="docutils literal"&gt;loop.run_in_executor&lt;/tt&gt;).&lt;/li&gt;
&lt;li&gt;plus le code dans &lt;tt class="docutils literal"&gt;compute_thumbnails&lt;/tt&gt; sera gourmand en CPU, et sera donc
long a exécuter, plus on gagnera en performance sur la version asynchrone par
rapport à la version synchrone, le temps de CPU étant &amp;quot;masqué&amp;quot; par le temps
du téléchargement des pages et des images.&lt;/li&gt;
&lt;li&gt;le code asynchrone est plus long, plus complexe, et nécessite de penser le
programme différemment.&lt;/li&gt;
&lt;li&gt;le debugging de code asynchrone est également plus complexe (voir &lt;a class="reference external" href="https://docs.python.org/3/library/asyncio-dev.html#debug-mode-of-asyncio"&gt;ici&lt;/a&gt;
et &lt;a class="reference external" href="https://pymotw.com/3/asyncio/debugging.html"&gt;là&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="attention-aux-pieges"&gt;
&lt;h2&gt;Attention aux pièges&lt;/h2&gt;
&lt;pre class="literal-block"&gt;
import asyncio
import time

async def foo():
    for i in range(10):
        await loop.run_in_executor(None, time.sleep, 1)

loop = asyncio.get_event_loop()
loop.run_until_complete(foo())
&lt;/pre&gt;
&lt;pre class="literal-block"&gt;
real    0m10.137s
user    0m0.079s
sys     0m0.017s
&lt;/pre&gt;
&lt;p&gt;Comment ça, 10 secondes&amp;nbsp;? Pourtant, les 10 appels à &lt;tt class="docutils literal"&gt;time.sleep(1)&lt;/tt&gt; semblent
asynchrones, non bloquants, concurrents, dans des &lt;cite&gt;coroutines&lt;/cite&gt; qui vont bien ?!&lt;/p&gt;
&lt;p&gt;Il y a un piège&amp;nbsp;: dans le code ci-dessus les 10 &lt;cite&gt;coroutines&lt;/cite&gt; sont exécutées
&lt;strong&gt;les unes après les autres&lt;/strong&gt;. Il pourrait être réécrit de la façon suivante,
qui met bien en valeur le problème&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import asyncio
import time

async def foo():
    await loop.run_in_executor(None, time.sleep, 1)

loop = asyncio.get_event_loop()
for i in range(10):
    loop.run_until_complete(foo())
&lt;/pre&gt;
&lt;p&gt;Lorsqu'une &lt;cite&gt;coroutine&lt;/cite&gt; se lance, on attend qu'elle se termine avant d'en lancer
une autre. La façon correcte d'écrire ce code est de lancer toutes les
&lt;cite&gt;coroutines&lt;/cite&gt; en même temps avec &lt;tt class="docutils literal"&gt;asyncio.wait()&lt;/tt&gt; ou &lt;tt class="docutils literal"&gt;asyncio.gather()&lt;/tt&gt;
comme ci-dessous&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
import asyncio
import time

async def foo():
    await loop.run_in_executor(None, time.sleep, 1)

loop = asyncio.get_event_loop()
tasks = [foo() for i in range(10)]
loop.run_until_complete(asyncio.wait(tasks))
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="asyncio-est-inutile-pour-cpu-bound"&gt;
&lt;h2&gt;Asyncio est inutile pour CPU-bound&lt;/h2&gt;
&lt;p&gt;La programmation asynchrone par &lt;cite&gt;coroutines&lt;/cite&gt; n'est utile que pour les cas
IO-bound&amp;nbsp;: lecture/écriture sur le système de fichier, sur un socket...&lt;/p&gt;
&lt;p&gt;Il faut imaginer un process comme étant Jean-Michel CPU, employé de Prog-corp,
auquel le programme demande d'exécuter une liste de tâches. Si Jean-Michel est
déjà surchargé de travail, réarranger ses tâches, les mettre dans le désordre,
bloquantes ou non bloquantes, ne changera rien du tout.&lt;/p&gt;
&lt;p&gt;Par contre, si Jean-Michel CPU est en train de se tourner les pouces pendant
que Bernard IO est en train de trimmer à transporter des paquets de gauche et
de droite, alors les choses peuvent être optimisées :&lt;/p&gt;
&lt;p&gt;En synchrone/bloquant&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, j'ai besoin d'un paquet steuplé&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: ok, &lt;strong&gt;bouge pas&lt;/strong&gt;, j'y vais&lt;/li&gt;
&lt;li&gt;... &amp;lt;un certain temps s'écoule&amp;gt; ...&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: pouf pouf, fatiguant tout ça, vla un paquet&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Jean-Michel CPU, tu m'ouvres ça steuplé, tu tries, tu ranges...&lt;/li&gt;
&lt;li&gt;Jean-Michel CPU&amp;nbsp;: ok, &lt;strong&gt;bouge pas&lt;/strong&gt;, je m'y met&lt;/li&gt;
&lt;li&gt;... &amp;lt;un certain temps s'écoule&amp;gt; ...&lt;/li&gt;
&lt;li&gt;Jean-Michel CPU&amp;nbsp;: la vache, y'avait du bouzin, vla j'ai fini&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, un autre paquet steuplé&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: ok, &lt;strong&gt;bouge pas&lt;/strong&gt;, j'y vais&lt;/li&gt;
&lt;li&gt;... &amp;lt;un certain temps s'écoule&amp;gt; ...&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En asynchrone/non-bloquant&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, j'ai besoin d'un paquet steuplé&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: ok, je te préviens quand je l'ai&lt;/li&gt;
&lt;li&gt;... &amp;lt;un certain temps s'écoule&amp;gt; ...&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: pouf pouf, fatiguant tout ça, vla un paquet&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, ok merci, file m'en chercher un autre, kthxbye&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Jean-Michel CPU, tu m'ouvres ça steuplé, tu tries, tu ranges...&lt;/li&gt;
&lt;li&gt;Jean-Michel CPU&amp;nbsp;: ok, je te préviens quand je me tourne les pouces&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: pouf pouf, fatiguant tout ça, vla un paquet&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, ok merci, file m'en chercher un autre, tu seras bien
urbain&lt;/li&gt;
&lt;li&gt;Jean-Michel CPU&amp;nbsp;: la vache, y'avait du bouzin, mais c'est bon j'ai fini&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Jean-Michel CPU, ah bah pas trop tôt, voilà un autre paquet&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Voilà un autre cas qui a l'air d'être IO-bound, sans que ce soit pourtant le
cas :&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, j'ai besoin du résultat de cette requête SQL&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: ok, je te préviens quand je l'ai&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: hop hop, le voilà&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: euh, pardon&amp;nbsp;? Déjà&amp;nbsp;!&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: ouais parce que en fait, on dirait pas, mais une database c'est
genre ultra méga hyper rapide, tavu&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, ok merci, file m'en chercher un autre, kthxbye&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: hop hop, le voilà&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: euh, oui, ok, mais euh, deux sec là, je suis occupé&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: hop hop, en voilà un autre&lt;/li&gt;
&lt;li&gt;Bernard IO&amp;nbsp;: hop hop, tiens, encore un&lt;/li&gt;
&lt;li&gt;Prog-corp&amp;nbsp;: Bernard IO, ouais non mais c'est bon, merci, attends un peu
steuplé, chuis débordé, et puis Jean-Michel CPU arrive pas à suivre de toute
manière&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les bases de données sont en général bien plus rapides que n'importe quel
programme écrit en python. Si, en théorie, une requête à la base de donnée est
une lecture/écriture (Input-Output), dans la pratique sa réponse arrive
tellement rapidement qu'il n'y a souvent rien à gagner à l'implémenter en
asynchrone. Si la base de données est distante, et que le délai (le round-trip)
est long, il est possible d'espérer gagner un peu. En général ce n'est pas le
cas (et si ça l'est, vous avez d'autres soucis à régler, en particulier au
niveau de la base de donnée elle-même). Pire, on perd le temps de la gestion
des &lt;cite&gt;coroutines&lt;/cite&gt;.&lt;/p&gt;
&lt;p&gt;La programmation asynchrone est vraiment efficace et utile dans quelques cas
notables, comme par exemple les lecture/écriture sur un système de fichier ou
sur un socket vers un serveur distant.&lt;/p&gt;
&lt;p&gt;Gérer des requêtes entrantes sur un serveur web de manière asynchrones grâce à
&lt;cite&gt;aiohttp&lt;/cite&gt;, ou des requêtes à postgresql avec &lt;cite&gt;aiopg&lt;/cite&gt; (&lt;a class="reference external" href="http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/"&gt;probablement inutile&lt;/a&gt;,
comme vu plus haut&amp;nbsp;?), ou avec le tout nouveau &lt;cite&gt;asyncpg&lt;/cite&gt;, et plus important que
tout, télécharger des photos de chat. Voilà les exemples les plus courants
croisés dans les tutoriels.&lt;/p&gt;
&lt;p&gt;Certains problèmes sont très pénibles à écrire de manière
synchrone/séquentielle, alors qu'ils s'expriment de manière tout à fait logique
de manière asynchrone. Par exemple un moteur de jeu&amp;nbsp;: une &lt;cite&gt;coroutine&lt;/cite&gt; qui gère
l'affichage en continu, et d'autres &lt;cite&gt;coroutines&lt;/cite&gt; pour récupérer/traiter les
entrées du joueur.&lt;/p&gt;
&lt;p&gt;Merci à &lt;a class="reference external" href="https://twitter.com/Alatitude77"&gt;Aurélien G.&lt;/a&gt; pour la &lt;a class="reference external" href="https://github.com/magopian/blog/pull/1"&gt;relecture et
réécriture de l'article&lt;/a&gt; afin de le
rendre plus agréable à lire !&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"></category></entry><entry><title>Latence et boucle de rétroaction</title><link href="//mathieu.agopian.info/blog/latence-et-boucle-de-retroaction.html" rel="alternate"></link><published>2014-07-14T13:56:00+02:00</published><updated>2014-07-14T13:56:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2014-07-14:/blog/latence-et-boucle-de-retroaction.html</id><summary type="html">&lt;div class="section" id="latence"&gt;
&lt;h2&gt;Latence&lt;/h2&gt;
&lt;p&gt;La &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Latence_%28informatique%29"&gt;latence&lt;/a&gt; en informatique est un délai minimum de transmission. C'est un des
principaux ennemis de la performance notamment dans le domaine du web.&lt;/p&gt;
&lt;p&gt;Un site web est généralement composé de multiples fichiers statiques (css,
javascript, images et icônes en tout genre). Pour afficher la totalité d'une
page, il …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="latence"&gt;
&lt;h2&gt;Latence&lt;/h2&gt;
&lt;p&gt;La &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Latence_%28informatique%29"&gt;latence&lt;/a&gt; en informatique est un délai minimum de transmission. C'est un des
principaux ennemis de la performance notamment dans le domaine du web.&lt;/p&gt;
&lt;p&gt;Un site web est généralement composé de multiples fichiers statiques (css,
javascript, images et icônes en tout genre). Pour afficher la totalité d'une
page, il faut donc faire de multiples requêtes au serveur, chacune prenant un
certain temps de traitement, à quoi on rajoute la latence (le temps de
transfert sur le réseau).&lt;/p&gt;
&lt;p&gt;On imagine facilement l'impact d'une forte latence lorsqu'il faut effectuer
plusieurs dizaines voire centaines de requêtes. Même si la latence n'est que de
10ms, si on multiple ça par 100, on atteint déjà une seconde.&lt;/p&gt;
&lt;p&gt;Prenons un autre exemple&amp;nbsp;: il n'est pas rare d'avoir des pages nécessitant des
dizaines (des centaines, voire des milliers&amp;nbsp;?) de requêtes SQL à une base de
données. Là encore, il faut multiplier ce nombre de requête par le temps de
traitement par la base de données, mais aussi par la latence.&lt;/p&gt;
&lt;p&gt;Étant donné la difficulté de réduire la latence, l'optimisation de la
performance passe par le réduction du nombre de requêtes&amp;nbsp;: on fait des
&lt;em&gt;bundles&lt;/em&gt; pour les fichiers statiques (on regroupe toutes les CSS ou les
fichiers javascript, on crée des &lt;em&gt;image map&lt;/em&gt; pour les icônes), on fait usage du
&lt;em&gt;JOIN&lt;/em&gt; pour les requêtes SQL...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="boucle-de-retroaction"&gt;
&lt;h2&gt;Boucle de rétroaction&lt;/h2&gt;
&lt;p&gt;La &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Boucle_de_r%C3%A9troaction"&gt;boucle de rétroaction&lt;/a&gt; (appelée «&amp;nbsp;feedback loop&amp;nbsp;» en anglais) permet de
raffiner un système afin d'arriver à un équilibre, à un objectif.&lt;/p&gt;
&lt;p&gt;Une boucle de rétroaction basée sur des mesures de position permettra à robot
d'atteindre la position souhaitée&amp;nbsp;: la vitesse et la direction seront adaptées
en fonction de la distance à l'objectif.&lt;/p&gt;
&lt;p&gt;Plus la boucle de rétroaction sera longue, plus l'équilibre sera long a
atteindre. En effet, si la mesure de position ne se fait qu'une fois toutes les
10 secondes, soit le robot devra se déplacer très lentement, soit faire de
nombreux aller-retours.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-rapport"&gt;
&lt;h2&gt;Le rapport&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;En tant qu'informaticien, il nous arrive régulièrement d'avoir à raffiner un
bout de code en fonction de différents paramètres&amp;nbsp;: l'expression du besoin, la
rapidité d'exécution, l'occupation mémoire...&lt;/p&gt;
&lt;p&gt;Et ce raffinage se fait par le biais d'une boucle de rétroaction qui peut
prendre différentes formes&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;des tests automatisés (test unitaires ou fonctionnels, TDD...)&lt;/li&gt;
&lt;li&gt;des tests manuels&lt;/li&gt;
&lt;li&gt;des simulations&lt;/li&gt;
&lt;li&gt;des aller-retours avec l'utilisateur final, le décideur, ...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si la latence s'invite dans ce mécanisme, on se retrouve dans la même situation
que le robot qui doit atteindre une position, mais qui n'a de retour sur sa
position que rarement. Soit on avance vite et on risque les aller-retours
(comprendre&amp;nbsp;: réécriture du code), soit on avance lentement.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, c'est un cauchemar.&lt;/p&gt;
&lt;p&gt;Voici quelques exemples de latence&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;besoin mal exprimé ou mal compris (la mesure de position n'est pas fiable)&lt;/li&gt;
&lt;li&gt;périmètre fonctionnel qui change (la position finale du robot change en cours
de route)&lt;/li&gt;
&lt;li&gt;grand nombre d'aller-retours avec l'utilisateur final/décideur (nécessité de
faire un très grand nombre de mesures de position)&lt;/li&gt;
&lt;li&gt;retours de l'utilisateur final/décideur très lents (mesure de position très
rare)&lt;/li&gt;
&lt;li&gt;dialogue avec une API/base de données/système distant/... très lent (chaque
mesure de position demande de très longs traitements)&lt;/li&gt;
&lt;li&gt;peu de confiance dans les résultats (nécessité de refaire plusieurs fois les
mesures ou de les retraiter)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="un-exemple-concret"&gt;
&lt;h2&gt;Un exemple concret&lt;/h2&gt;
&lt;p&gt;Avec mon collègue &lt;a class="reference external" href="https://larlet.fr/david/"&gt;David&lt;/a&gt; nous nous sommes chargés de la résolution d'un ticket
pour le projet &lt;a class="reference external" href="https://addons.mozilla.org"&gt;AMO&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Afin de pouvoir présenter des graphiques d'utilisation/téléchargement des
extensions à leur auteur (comme pour &lt;a class="reference external" href="https://addons.mozilla.org/en-US/firefox/addon/firebug/statistics/?last=30"&gt;Firebug&lt;/a&gt;), toutes les requêtes de
téléchargement et de demande de mise à jour sont enregistrées et stockées dans
une base de données «&amp;nbsp;big data&amp;nbsp;» (pour les curieux&amp;nbsp;: c'est stocké dans &lt;a class="reference external" href="http://hadoop.apache.org/"&gt;Hadoop&lt;/a&gt;
et récupéré par le biais de &lt;a class="reference external" href="https://hive.apache.org/"&gt;Hive&lt;/a&gt;). On parle de plus d'un milliard de requête
par jour, toutes requêtes confondues.&lt;/p&gt;
&lt;p&gt;Ce qui nous a d'abord paru simple et rapide à implémenter, s'est transformé en
deux semaines de sprint (et n'est pas encore terminé).&lt;/p&gt;
&lt;p&gt;Nous avons subit et fait partie des différentes formes de latences listées
ci-dessus&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;besoin mal exprimé ou mal compris&amp;nbsp;: la répartition des tâches entre nous et
notre interlocuteur a changé plusieurs fois. Par ailleurs nous n'avions
aucune connaissance du système avant de nous y attaquer.&lt;/li&gt;
&lt;li&gt;périmètre fonctionnel qui change&amp;nbsp;: malheureusement, arrivé au bout de la
première semaine de sprint, nous avons appris qu'il nous fallait refaire la
moitié de notre travail différemment suite à des contraintes non prévues.&lt;/li&gt;
&lt;li&gt;grand nombre d'aller-retours avec l'utilisateur final/décideur&amp;nbsp;: à l'heure de
l'écriture de ce billet, nous en sommes à 54 commentaires sur le ticket.&lt;/li&gt;
&lt;li&gt;retours de l'utilisateur final/décideur très lent&amp;nbsp;: nous travaillons en
France, et notre interlocuteur sur la côte ouest des USA (-9h). Il est
fréquent d'avoir besoin d'attendre le lendemain pour avoir une réponse, dans
un sens ou dans l'autre.&lt;/li&gt;
&lt;li&gt;dialogue avec un système distant très lent&amp;nbsp;: de par le nombre de données à
traiter, chaque requête à Hive (au nombre de 6) prend en moyenne 15 minutes,
et la taille des données à télécharger varie entre 500Mo et 1.6Go.&lt;/li&gt;
&lt;li&gt;peu de confiance dans les résultats&amp;nbsp;: nous essayions de mettre au point les
requêtes Hive à exécuter, et par le même temps, le post-traitement de ces
requêtes, pour coller au plus proche aux statistiques et graphiques attendus.
Jouer sur deux paramètres en même temps est déjà malaisé en temps normal,
mais là il nous était de plus très laborieux de confronter nos résultats avec
ceux de la production.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="la-solution"&gt;
&lt;h2&gt;La solution&lt;/h2&gt;
&lt;p&gt;Diminuer la latence dans la boucle de rétroaction, par tous les moyens
possibles.&lt;/p&gt;
&lt;p&gt;De la même manière qu'il arrive régulièrement de lancer un interpréteur Python
(ou un &lt;em&gt;ipdb&lt;/em&gt; ;) pour bidouiller et expérimenter avec un petit bout de code
et avoir des retours immédiats, il faut tout faire pour accélérer la boucle de
rétroaction.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;ça paraît évident, mais connaître précisément le besoin et le contexte avant
de se lancer est primordial. Nous avons facilement perdu deux à trois jours à
cause de ça, en début de sprint, avec notre envie d'avancer rapidement (et de
passer à quelque chose de plus fun ;)&lt;/li&gt;
&lt;li&gt;périmètre fonctionnel qui change&amp;nbsp;: difficile de le prévoir ou de l'éviter,
mais je pense qu'en ayant une boucle de rétroaction plus courte, nous aurions
eu des résultats plus rapidement, et aurions alors plus rapidement rencontré
les contraintes qui ont fait changer le périmètre.&lt;/li&gt;
&lt;li&gt;le nombre d'aller-retours avec l'interlocuteur, et leur lenteur&amp;nbsp;: quand nous
avons pu mettre en place un appel vidéo journalier, beaucoup de choses se
sont débloquées.&lt;/li&gt;
&lt;li&gt;dialogue avec le système distant très lent&amp;nbsp;: nous aurions dû &lt;a class="reference external" href="http://www.voidspace.org.uk/python/mock/"&gt;mock&lt;/a&gt; beaucoup
plus tôt le retour des requêtes Hive. Nous aurions ensuite dû récupérer un
jeu de donnée complet (plusieurs Go) le plus tôt possible pour faire des
tests sur le post-traitement dans un premier temps, puis ensuite seulement
essayer d'améliorer les requêtes Hive. Faire les deux en même temps est une
erreur qui nous a coûté de nombreux appels à Hive (et donc de nombreuses
attentes et rétro-pédalages).&lt;/li&gt;
&lt;li&gt;peu de confiance dans les résultats&amp;nbsp;: encore une fois vu la taille des
données à traiter, il nous était très difficile de confronter nos données à
celles attendues (uniquement disponibles en production). Nous avons fini par
mettre en production un post-traitement parallèle à l'actuel, et stocker les
données dans d'autres tables, en attendant d'avoir la version finale et
raffinée de l'algorithme et des requêtes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et à ma plus grande honte, ne pas avoir de tests unitaires a été un boulet
supplémentaire&amp;nbsp;: lancer un post-traitement pour s'apercevoir 15 minutes plus
tard qu'on a oublié une virgule dans le code...&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Heartbleed, conséquences pour les utilisateurs</title><link href="//mathieu.agopian.info/blog/heartbleed-consequences-pour-les-utilisateurs.html" rel="alternate"></link><published>2014-04-09T09:11:00+02:00</published><updated>2014-04-09T09:11:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2014-04-09:/blog/heartbleed-consequences-pour-les-utilisateurs.html</id><summary type="html">&lt;div class="section" id="le-probleme"&gt;
&lt;h2&gt;Le problème&lt;/h2&gt;
&lt;p&gt;Hier, le 8 avril 2014, une énorme faille de sécurité a été divulguée, nom de
code Heartbleed (plus d'infos sur &lt;a class="reference external" href="http://heartbleed.com"&gt;http://heartbleed.com&lt;/a&gt;). Elle impacte OpenSSL,
qui est la technologie qui permet de sécuriser les échanges entre notre
navigateur et les sites qui utilisent des adresses qui commencent …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="le-probleme"&gt;
&lt;h2&gt;Le problème&lt;/h2&gt;
&lt;p&gt;Hier, le 8 avril 2014, une énorme faille de sécurité a été divulguée, nom de
code Heartbleed (plus d'infos sur &lt;a class="reference external" href="http://heartbleed.com"&gt;http://heartbleed.com&lt;/a&gt;). Elle impacte OpenSSL,
qui est la technologie qui permet de sécuriser les échanges entre notre
navigateur et les sites qui utilisent des adresses qui commencent par
&amp;quot;&lt;a class="reference external" href="https://"&gt;https://&lt;/a&gt;&amp;quot; (notez bien le &amp;quot;s&amp;quot; final). On estime qu'elle impacte environ 2/3
des sites sur internet.&lt;/p&gt;
&lt;p&gt;Ces sites qui utilisent SSL apparaissent avec un petit cadenas à gauche de leur
adresse, dans la barre d'adresse de votre navigateur, indiquant que la
connexion est sécurisée. C'est par exemple le cas des sites de banque, de mail,
mais aussi de la plupart des pages de connexion avec mot de passe, et de
paiement par carte bancaire.&lt;/p&gt;
&lt;p&gt;Cette sécurité permet de chiffrer toutes les communications entre le navigateur
et le site, empêchant quiconque de pouvoir espionner (par exemple en &amp;quot;sniffant&amp;quot;
le wifi sur lequel vous êtes connectés) et récupérer vos mots de passe.&lt;/p&gt;
&lt;p&gt;Seulement voilà, hier on a appris que cette sécurité comportait un bug logiciel
qui permet à n'importe qui de récupérer les mots de passe des utilisateurs et
plein d'autres informations sur les sites qui utilisent cette sécurité.&lt;/p&gt;
&lt;p&gt;Petite note pour les utilisateurs de Google Chrome&amp;nbsp;: je vous recommande
d'utiliser &lt;a class="reference external" href="http://www.mozilla.org/fr/firefox/new/"&gt;Firefox&lt;/a&gt;. Si vous souhaitez continuer à utiliser Chrome, veillez à activer
l'option &lt;a class="reference external" href="https://support.google.com/chrome/answer/100214?hl=fr"&gt;vérifier la révocation du certificat serveur&lt;/a&gt;. Malheureusement,
Chrome a choisi de ne pas activer cette vérification par défaut pour permettre
aux pages de se charger plus vite... au dépends de votre sécurité donc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ce-qu-il-faut-faire"&gt;
&lt;h2&gt;Ce qu'il faut faire&lt;/h2&gt;
&lt;p&gt;La plupart des sites ont dû mettre en place un correctif à présent, en tout cas
je l'espère. Pour le vérifier, avant de vous connecter sur un site (n'importe
quel site !!!), vérifiez d'abord qu'il n'est pas (ou plus) vulnérable en
entrant son adresse sur le site suivant : &lt;a class="reference external" href="http://filippo.io/Heartbleed/"&gt;http://filippo.io/Heartbleed/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, je vous recommande de vous déconnecter et reconnecter sur
tous les sites sur lesquels vous êtes déjà connectés, pour être sûr d'avoir la
version la plus à jour de leur connexion SSL, puis de changer vos mots de passe
(oui, tous). Ça va être long et pénible, mais c'est nécessaire. Faites bien la
liste des sites sur lesquels vous vous êtes connectés hier (le 8 avril 2014),
et surveillez bien vos comptes sur ces sites, que vous ne voyez rien de bizarre
qui pourrait être causé par un pirate qui aurait récupéré vos accès (ajout de
bénéficiaires de virement sur votre site bancaire, de nouvelles adresses
d'expédition sur les sites de vente en ligne...).&lt;/p&gt;
&lt;p&gt;Pour rappel, deux choses qui vous simplifient la vie concernant les mots de
passe :&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;sur Firefox vous pouvez sauvegarder vos mots de passe dans le navigateur, et
il faut bien penser à activer le &lt;a class="reference external" href="https://support.mozilla.org/fr/kb/utiliser-mot-passe-principal-proteger-identifiants"&gt;mot de passe général&lt;/a&gt; : un mot de
passe qui permet de chiffrer tous les mots de passe que vous
avez enregistrés, pour que quelqu'un qui récupère votre ordinateur (suite à
un vol, parce qu'il est en réparation, en prêt...) ne puisse pas voir tous
vos mots de passe enregistrés&lt;/li&gt;
&lt;li&gt;sur Firefox il est possible de sauvegarder/synchroniser tous ses mots de
passe enregistrés, ses extensions installées, ses onglets ouverts et ses
favoris sur un serveur entièrement chiffré et sécurisé de Mozilla en
utilisant la fonctionnalité &lt;a class="reference external" href="https://support.mozilla.org/fr/kb/comment-configurer-firefox-sync"&gt;firefox sync&lt;/a&gt;, que je vous recommande. C'est
très pratique quand on a plusieurs ordinateurs/téléphones/tablettes avec
firefox. Faites bien attention de sauvegarder la &amp;quot;clé de récupération Sync&amp;quot;
pour pouvoir vous reconnecter à votre compte Sync si jamais vous changez
d'ordinateur (c'est un fichier qu'il faut sauvegarder et garder en lieu
sûr).&lt;/li&gt;
&lt;li&gt;il existe de multiples logiciels de gestion de mots de passe, comme
&lt;a class="reference external" href="https://www.keepassx.org/"&gt;KeePassX&lt;/a&gt;, &lt;a class="reference external" href="https://agilebits.com/onepassword"&gt;1password&lt;/a&gt; ou &lt;a class="reference external" href="http://revelation.olasagasti.info/"&gt;revelation&lt;/a&gt; (pour linux). Ils permettent
d'enregistrer les mots de passe pour tous ses sites, et les sauvegarder dans
un fichier chiffré. Il faut bien sauvegarder ce fichier (comme pour la clé
de récupération Sync). C'est très pratique pour retenir tous ses mots de
passe, en sachant que vous êtes sensé(e)s avoir un mot de passe sécurisé et
différent sur chacun des sites que vous fréquentez. Autrement, si un de ces
sites se fait pirater, le pirate peut réutiliser votre mot de passe sur tous
les autres sites que vous utilisez... Et les sites qui se font pirater
chaque année sont légion, et il existe des bases de données publiques de
tous les comptes et mots de passe déjà piratés/connus.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour terminer, sur les sites qui le proposent, activez l'authentification en
deux étapes, comme par exemple sur gmail. C'est une vérification en deux temps
lorsque vous vous connectez ou que vous voulez faire une action sensible sur un
site. Dans un premier temps vous entrez votre mot de passe (qui aurait pu être
piraté), et dans un deuxième temps, vous fournissez un code qui vous a été
donné lors de votre connexion par un autre moyen. Par exemple dans le cas de
gmail, vous installez une application &amp;quot;google authenticator&amp;quot; sur votre
téléphone, qui génère une nouvelle séquence de chiffres chaque minute. Le
pirate doit donc avoir votre mot de passe ET votre téléphone si il veut se
connecter sur votre compte.&lt;/p&gt;
&lt;p&gt;Plusieurs sites bancaires permettent aussi cette authentification en deux
étapes.&lt;/p&gt;
&lt;p&gt;Allez, c'est maintenant l'heure de changer tous vos mots de passe, courage&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>DjangoCon Europe</title><link href="//mathieu.agopian.info/blog/djangocon-europe.html" rel="alternate"></link><published>2014-01-28T08:08:00+01:00</published><updated>2014-01-28T08:08:00+01:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2014-01-28:/blog/djangocon-europe.html</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;are you an english reader? If so, please scroll down to the &lt;a class="reference internal" href="#english-version"&gt;english
version&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="version-francaise"&gt;
&lt;h2&gt;Version française&lt;/h2&gt;
&lt;p&gt;La communauté &lt;a class="reference external" href="https://djangoproject.com"&gt;Django&lt;/a&gt; européenne va se réunir comme chaque année, mais cette
année, l'édition de cette grande rencontre va se dérouler en France. Plus
précisément, sur une île, l'île des Embiez, dans le …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;are you an english reader? If so, please scroll down to the &lt;a class="reference internal" href="#english-version"&gt;english
version&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="version-francaise"&gt;
&lt;h2&gt;Version française&lt;/h2&gt;
&lt;p&gt;La communauté &lt;a class="reference external" href="https://djangoproject.com"&gt;Django&lt;/a&gt; européenne va se réunir comme chaque année, mais cette
année, l'édition de cette grande rencontre va se dérouler en France. Plus
précisément, sur une île, l'île des Embiez, dans le var.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://2014.djangocon.eu/"&gt;DjangoCon Europe&lt;/a&gt; (pour les français qui ne le savent pas déjà, DjangoCon est
l'abréviation de «&amp;nbsp;Django Conference&amp;nbsp;» en anglais) est un évènement
incontournable, qui permet de regrouper plusieurs centaines de participants sur
un même lieu, pendant 3 à 5 jours (3 jours de conférences et présentations, 2
jours de sprints).&lt;/p&gt;
&lt;div class="section" id="pourquoi-venir"&gt;
&lt;h3&gt;Pourquoi venir&lt;/h3&gt;
&lt;p&gt;Ce qui fait notre force, c'est ce qui entretien notre passion du travail bien
fait, notre volonté de se former chaque jour un peu plus sur notre outil, notre
désir d'en apprendre toujours plus. Et pour cela, quoi de mieux que de se
retrouver avec ses pairs, pour échanger et partager sur notre travail au
quotidien&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Ces rencontres permettent de se faire connaître, de faire des connaissances, de
trouver de futurs clients, un employeur, ou son prochain collègue de travail.&lt;/p&gt;
&lt;p&gt;Elles permettent aussi de se tenir informé des dernières évolutions du langage
ou du &lt;em&gt;framework&lt;/em&gt;, ou tout simplement de se faire une meilleure idée des cas
d'utilisations, des soucis rencontrés et résolus par d'autres, des techniques
et astuces à des problèmes de son quotidien.&lt;/p&gt;
&lt;p&gt;Et ne vous faites pas d'illusion, vos futurs employeurs, partenaires, clients,
ne s'y tromperont pas&amp;nbsp;: un développeur passionné, qui participe à la
communauté, aux rencontres, est d'autant plus attirant et intéressant&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="qui-doit-venir"&gt;
&lt;h3&gt;Qui doit venir&lt;/h3&gt;
&lt;p&gt;Cette conférence s'adresse à tous&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;les débutants, qui vont en apprendre énormément en quelques jours&lt;/li&gt;
&lt;li&gt;les curieux qui auront une bonne vision d'ensemble de l'outil, de son
utilisation et des problèmes qu'il permet de résoudre&lt;/li&gt;
&lt;li&gt;les confirmés qui trouveront eux aussi leur compte&lt;/li&gt;
&lt;li&gt;les indépendants qui peuvent &lt;em&gt;networker&lt;/em&gt; et se trouver de futurs partenaires
ou clients&lt;/li&gt;
&lt;li&gt;les employés qui pourront peut-être trouver leur prochain collègue de travail&lt;/li&gt;
&lt;li&gt;les personnes en recherche de travail qui auront une chance de se présenter
en personne aux recruteurs, faire connaissance avec leur future équipe&lt;/li&gt;
&lt;li&gt;les entreprises qui souhaitent que leurs employés restent à la pointe, se
forment en continu&lt;/li&gt;
&lt;li&gt;les sponsors qui vont augmenter leur visibilité, se faire connaître des
développeurs Django, donner un peu à la communauté qui les fait vivre,
trouver plus facilement de futurs employés&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="cette-conference-c-est-toi"&gt;
&lt;h3&gt;Cette conférence, c'est toi&lt;/h3&gt;
&lt;p&gt;Oui, toi lecteur, on compte sur toi, la communauté compte sur toi. Une
communauté n'est rien sans ses membres, une rencontre n'est rien sans ses
participants. Non seulement je te demande de venir, parce que j'ai envie de te
rencontrer, d'en apprendre plus sur toi, sur ton travail, mais je te demande
aussi de participer activement à cette rencontre.&lt;/p&gt;
&lt;p&gt;Je te demande de proposer un sujet de conférence.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pourquoi-proposer-un-sujet"&gt;
&lt;h3&gt;Pourquoi proposer un sujet&lt;/h3&gt;
&lt;p&gt;Parce-que c'est bien pour la communauté, pour la rencontre, mais aussi pour
toi&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;tu es débutant&amp;nbsp;? Qui mieux que toi peut parler à d'autres débutants, leur
faire partager ton expérience, les obstacles que tu as rencontrés, ou les
pépites que tu as trouvées au cours de ton apprentissage&lt;/li&gt;
&lt;li&gt;tu es confirmé&amp;nbsp;? Qui mieux que toi peut parler de cette librairie que tu as
découverte (ou écrite), de ce qu'elle pourrait apporter aux autres&lt;/li&gt;
&lt;li&gt;tu es un expert&amp;nbsp;? Qui mieux que toi peut présenter une technique avancée, une
fonctionnalité méconnue de Python ou Django&lt;/li&gt;
&lt;li&gt;tu travaille sur des projets pointus, tu as rencontré des obstacles ou des
problèmes que tu as résolus&amp;nbsp;? Qui mieux que toi peut permettre à ses pairs
d'en apprendre plus, partager ton expérience pour qu'elle bénéficie au plus
grand nombre&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et si tu n'as encore jamais parlé, présenté, lors d'une rencontre, n'hésite
pas, c'est le moment de s'y mettre. Il y a une première fois à tout, et
présenter devant cette communauté que je sais être courtoise, compatissante,
patiente et encourageante ne peut être que bénéfique et une excellente première
expérience.&lt;/p&gt;
&lt;p&gt;Et si tu es un orateur confirmé et habitué, n'hésite pas non plus. Nous n'en
avons sûrement pas marre de t'entendre, nous n'attendons qu'une chose, c'est
qu'une fois de plus tu fasse don de ton temps et de ton expérience.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-tres-vite"&gt;
&lt;h3&gt;À très vite&lt;/h3&gt;
&lt;p&gt;Cette rencontre sera ce que tu y apporte. Inscris toi, propose un sujet,
sponsorise. Cette rencontre, c'est toi.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="english-version"&gt;
&lt;h2&gt;English version&lt;/h2&gt;
&lt;p&gt;(Thanks a lot &lt;a class="reference external" href="http://medicine.cf.ac.uk/person/mr-daniele-marco-procida/"&gt;Daniele Procida&lt;/a&gt; for the translation!)&lt;/p&gt;
&lt;p&gt;Once again the European &lt;a class="reference external" href="https://djangoproject.com"&gt;Django&lt;/a&gt; community will get together as it does every
year, but this year's edition of its great annual gathering will take place in
France. More precisely, on an island, the Îsle des Embiez, in the mediterranean
department of the Var.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://2014.djangocon.eu/"&gt;DjangoCon Europe&lt;/a&gt; is an unmissable event, that gives several hundred
participants a chance to get together in the same place, for 3 to 5 days (3
days of talks and meetings, 2 days of sprints).&lt;/p&gt;
&lt;div class="section" id="why-you-should-attend"&gt;
&lt;h3&gt;Why you should attend&lt;/h3&gt;
&lt;p&gt;Our strength is nurtured by our passion for a job well done, our eagerness
always to become more skilled with the tools of our trade, our desire to learn
more. And what could be a better way of doing that than to meet our peers, to
exchange and share what we know for the benefit of our daily work.&lt;/p&gt;
&lt;p&gt;These conferences provide an opportunity to meet new people, to find new
clients, an employer or one's next colleague.&lt;/p&gt;
&lt;p&gt;They also provide an excellent way to keep up-to-date with the latest
developments in the language or the framework, or simply to acquire a better
understanding of its use cases, of problems that others have solved, or tips
and techniques that will help on a day-to-day basis.&lt;/p&gt;
&lt;p&gt;And don't be under any doubt: your future employers, collaborators, clients
will be well aware of what it means: a passionate developer, who takes part in
community events, is even more attractive and interesting!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="who-should-come"&gt;
&lt;h3&gt;Who should come&lt;/h3&gt;
&lt;p&gt;This conference is aimed at everyone:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;beginners, who'll learn a huge amount in a few days&lt;/li&gt;
&lt;li&gt;those eager to learn more, who'll gain an excellent overview of the toolbase,
its use and the problems it can be applied to&lt;/li&gt;
&lt;li&gt;skilled, who will also find their interest&lt;/li&gt;
&lt;li&gt;freelancers, who'll have opportunities for networking, and finding new
partners or clients&lt;/li&gt;
&lt;li&gt;employees, who may discover their next new co-worker&lt;/li&gt;
&lt;li&gt;those seeking new employment opportunities, who'll have a chance to meet
recruiters in person, and meet their future colleagues&lt;/li&gt;
&lt;li&gt;organisations that need their staff to be at the forefront of developments
and always learning&lt;/li&gt;
&lt;li&gt;sponsors, who want to increase their visibility, to become better known
amongst Django developers, to give a bit back to the community, to find new
members of staff&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="you-are-this-conference"&gt;
&lt;h3&gt;You are this conference&lt;/h3&gt;
&lt;p&gt;Yes, you the reader - we count on you, the community counts on you. A community
is nothing without its members, a conference is nothing without its
participants. I invite you not just because I want to meet you, to learn more
about you and your work; I ask you to join in and take an active part in this
event.&lt;/p&gt;
&lt;p&gt;I invite you to propose a talk.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="why-propose-a-talk"&gt;
&lt;h3&gt;Why propose a talk?&lt;/h3&gt;
&lt;p&gt;Because it's good for the community, for the conference, but also for you:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;are you a beginner? What better than to speak to other beginners, to share
with them your experience, the obstacles you encountered, and the secrets you
discovered while learning&lt;/li&gt;
&lt;li&gt;are you experienced? Who better than you to speak of a library you've
discovered (or written), and to explain it to others?&lt;/li&gt;
&lt;li&gt;are you an expert? Who better than you to present an advanced technique, a
little-known functionality in Python or Django?&lt;/li&gt;
&lt;li&gt;you work on specialised projects, you have encountered obstacles or problems
and solved them? Who better than you to give your peers a chance to learn
something new, sharing your experience so that it benefits the greatest
number?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And if you've never spoken or given a presentation to a conference before,
don't let that put you off: this is the perfect opportunity to start. There's a
first time for everything, and to give a talk to this particular community -
one that's known for being welcoming, sympathetic, patient and courteous - will
help make sure that it's an excellent experience for you.&lt;/p&gt;
&lt;p&gt;And if you're an experienced and regular speaker, don't hesitate either. We're
certainly not tired of hearing from you, and we ask of you just one thing: that
once more you share what you know with us.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="see-you-soon"&gt;
&lt;h3&gt;See you soon&lt;/h3&gt;
&lt;p&gt;This conference will be what you bring to it. Sign up, propose a talk, become a
sponsor. This conference: it's you.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="django"></category></entry><entry><title>Objets ou fonctions</title><link href="//mathieu.agopian.info/blog/objets-ou-fonctions.html" rel="alternate"></link><published>2013-12-16T13:39:00+01:00</published><updated>2013-12-16T13:39:00+01:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-12-16:/blog/objets-ou-fonctions.html</id><summary type="html">&lt;p&gt;Objets ou fonctions&amp;nbsp;? Voilà une question que je ne me pose pas assez souvent.&lt;/p&gt;
&lt;p&gt;De part mon utilisation d'un langage objet (&lt;a class="reference external" href="http://python.org"&gt;Python&lt;/a&gt;), et d'un framework web
(&lt;a class="reference external" href="https://djangoproject.com"&gt;Django&lt;/a&gt;) basé sur des objets (utilisation d'un &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/topics/db/"&gt;ORM&lt;/a&gt;, de &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/topics/class-based-views/"&gt;vues génériques&lt;/a&gt;
sous forme de classes), je ne me pose quasiment jamais la question&amp;nbsp;: j'utilise …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Objets ou fonctions&amp;nbsp;? Voilà une question que je ne me pose pas assez souvent.&lt;/p&gt;
&lt;p&gt;De part mon utilisation d'un langage objet (&lt;a class="reference external" href="http://python.org"&gt;Python&lt;/a&gt;), et d'un framework web
(&lt;a class="reference external" href="https://djangoproject.com"&gt;Django&lt;/a&gt;) basé sur des objets (utilisation d'un &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/topics/db/"&gt;ORM&lt;/a&gt;, de &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/topics/class-based-views/"&gt;vues génériques&lt;/a&gt;
sous forme de classes), je ne me pose quasiment jamais la question&amp;nbsp;: j'utilise
par défaut des objets.&lt;/p&gt;
&lt;p&gt;Seulement, depuis mes explorations du côté de langages fonctionnels (&lt;a class="reference external" href="http://clojure.org"&gt;Clojure&lt;/a&gt;,
&lt;a class="reference external" href="http://erlang.org/"&gt;Erlang&lt;/a&gt; et &lt;a class="reference external" href="http://elixir-lang.org/"&gt;Elixir&lt;/a&gt;), je me rends compte que la question mérite vraiment d'être
posée.&lt;/p&gt;
&lt;div class="section" id="un-exemple-concret"&gt;
&lt;h2&gt;Un exemple concret&lt;/h2&gt;
&lt;p&gt;Pour le reste de cet article, je vais me baser sur l'exemple concret du &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Jeu_de_la_vie"&gt;jeu de
la vie de Conway&lt;/a&gt; qui était le sujet de la &lt;a class="reference external" href="http://gdcr13.coderetreat-marseille.org/"&gt;journée de code retreat à
Marseille&lt;/a&gt; à laquelle j'ai assisté ce samedi 14 Décembre.&lt;/p&gt;
&lt;p&gt;Voici deux implémentations, avec des objets puis avec des fonctions, pour
étayer le discours. Je ne prétends pas fournir le code parfait, mais uniquement
un support de discussion.&lt;/p&gt;
&lt;p&gt;J'ai essayé de reprendre le même code (parcours de la grille, recherche des
cellules voisines, calcul de la vie ou mort de la cellule) lorsque c'était
possible pour avoir le plus de points de comparaison possibles.&lt;/p&gt;
&lt;div class="section" id="avec-des-objets"&gt;
&lt;h3&gt;Avec des objets&lt;/h3&gt;
&lt;p&gt;J'ai opté pour un découpage «&amp;nbsp;raisonnable&amp;nbsp;» (on a fait une session avec un
&lt;tt class="docutils literal"&gt;CellContext&lt;/tt&gt; qui s'occupait de gérer les cellules voisines, au lieu de
grouper ça directement dans l'objet &lt;tt class="docutils literal"&gt;Cell&lt;/tt&gt;, mais je trouvais ça un brin trop
&lt;em&gt;over-engineered&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;La logique est d'initialiser une fois pour toutes les cellules voisines pour
chaque cellule, ce qui simplifie le comptage des voisines en vie.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;WORLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# False = dead cell, True = live cell&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;alive&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_neighbour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_num_live_neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;neighbours&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;live_neighbours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_num_live_neighbours&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;survives&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;live_neighbours&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;born&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;live_neighbours&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;born&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;survives&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_state&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_state&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'O'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;World&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;neighbours_rel_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# relative position of all possible neighbours&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# upper row&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# same row&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;# lower row&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# initialize neighbours for all cells&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;neighbours_rel_pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;pos_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_x&lt;/span&gt;
                    &lt;span class="n"&gt;pos_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_y&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos_x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pos_x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
                            &lt;span class="n"&gt;pos_y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pos_y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])):&lt;/span&gt;
                        &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_neighbour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos_x&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;pos_y&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# compute next state&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# apply&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                         &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;67 lignes, 2 objets, 9 méthodes.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="avec-des-fonctions"&gt;
&lt;h3&gt;Avec des fonctions&lt;/h3&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;WORLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# False = dead cell, True = live cell&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evolve_world&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;new_world&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# copy the current world&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_world&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cell&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;new_world&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evolve_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_world&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                                          &lt;span class="n"&gt;num_alive_neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new_world&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evolve_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell_alive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_neighbours&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;cell_alive&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;num_neighbours&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cell_alive&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;num_neighbours&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;num_alive_neighbours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;neighbours_rel_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# relative position of all possible neighbours&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# upper row&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# same row&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  &lt;span class="c1"&gt;# lower row&lt;/span&gt;

    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;neighbours_rel_pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pos_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_x&lt;/span&gt;
        &lt;span class="n"&gt;pos_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;neigh_pos_y&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos_x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pos_x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
                &lt;span class="n"&gt;pos_y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pos_y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
                &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos_x&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;pos_y&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;world_tostring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;O&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alive&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;43 lignes, 4 fonctions.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="le-role-des-donnees"&gt;
&lt;h2&gt;Le rôle des données&lt;/h2&gt;
&lt;div class="section" id="id1"&gt;
&lt;h3&gt;Avec des objets&lt;/h3&gt;
&lt;p&gt;En &lt;abbr title="Programmation Orientée Objet"&gt;POO&lt;/abbr&gt;, on scinde la donnée d'entrée
(base de données, fichiers, flux de données...) pour la répartir dans
différents objets. Dans notre exemple, un objet &lt;tt class="docutils literal"&gt;World&lt;/tt&gt; qui stocke l'ensemble
des cellules, et un objet &lt;tt class="docutils literal"&gt;Cell&lt;/tt&gt; qui stocke son état (en vie ou morte) et
l'ensemble de ses voisines.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;+&lt;/strong&gt; Représentation mentale aisée des différentes entitées&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+&lt;/strong&gt; Répartition des responsabilités&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-&lt;/strong&gt; Verbosité&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-&lt;/strong&gt; Difficulté pour les tests&amp;nbsp;: il faut gérer les
&lt;abbr title="données pour l'initialisation des objets"&gt;fixtures&lt;/abbr&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="id2"&gt;
&lt;h3&gt;Avec des fonctions&lt;/h3&gt;
&lt;p&gt;En fonctionnel, on traite directement la donnée d'entrée par des étapes
successives et différentes fonctions que l'on compose.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;+&lt;/strong&gt; Concision&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+&lt;/strong&gt; Moins de code à maintenir, moins de code à lire et comprendre&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+&lt;/strong&gt; Facilité pour les tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-&lt;/strong&gt; Duplication de la donnée (nouveau monde à chaque itération)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-&lt;/strong&gt; Recalcul des voisins à chaque itération&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-reutilisation-du-code"&gt;
&lt;h2&gt;La réutilisation du code&lt;/h2&gt;
&lt;p&gt;Dans notre exemple très basique, pas de réutilisation du code. Pas d'héritage
pour les objets, pas de composition de fonctions.&lt;/p&gt;
&lt;div class="section" id="id3"&gt;
&lt;h3&gt;Avec des objets&lt;/h3&gt;
&lt;p&gt;La réutilisation du code dans la POO se fait principalement par l'héritage
d'objets.&lt;/p&gt;
&lt;p&gt;Imaginons que nous ayons demain un monde différent, qui au lieu d'être
représenté par un tableau de cellules carrées, soit un amas de cellules
hexagonales. On pourrait alors avoir un objet &lt;tt class="docutils literal"&gt;HexagonalWorld&lt;/tt&gt; qui hériterait
de &lt;tt class="docutils literal"&gt;World&lt;/tt&gt; et redéfinirait les méthodes &lt;tt class="docutils literal"&gt;__init__&lt;/tt&gt; et &lt;tt class="docutils literal"&gt;__str__&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Le reste du code resterait le même, et serait donc réutilisé.&lt;/p&gt;
&lt;p&gt;On peut encore imaginer des cellules plus ou moins résistantes qui, en
redéfinissant &lt;tt class="docutils literal"&gt;mutate&lt;/tt&gt; auraient des règles différentes de vie ou de mort.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h3&gt;Avec des fonctions&lt;/h3&gt;
&lt;p&gt;La réutilisation du code dans la programmation fonctionnelle se fait par la
composition de fonctions.&lt;/p&gt;
&lt;p&gt;On aurait pu imaginer partir d'un format différent pour le monde, sous la forme
d'une suite de &lt;tt class="docutils literal"&gt;0&lt;/tt&gt; et de &lt;tt class="docutils literal"&gt;1&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;On aurait alors tout d'abord transformé chaque &lt;tt class="docutils literal"&gt;0&lt;/tt&gt; ou &lt;tt class="docutils literal"&gt;1&lt;/tt&gt; en booléen puis
découpé cette suite en lignes d'une longueur donnée, composant deux fonctions&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
data = &amp;quot;001001100&amp;quot;
to_bool(data) == [False, False, True, False, False, True, True, False, False]
to_grid(to_bool(data)) == [
    [False, False, True],
    [False, False, True],
    [True, False, False]]
&lt;/pre&gt;
&lt;p&gt;Si ce n'est pas d'une série de &lt;tt class="docutils literal"&gt;0&lt;/tt&gt; et de &lt;tt class="docutils literal"&gt;1&lt;/tt&gt; qu'on part, mais de &lt;tt class="docutils literal"&gt;X&lt;/tt&gt; et
de &lt;tt class="docutils literal"&gt;O&lt;/tt&gt;, on change la fonction &lt;tt class="docutils literal"&gt;to_bool&lt;/tt&gt;, la fonction &lt;tt class="docutils literal"&gt;to_grid&lt;/tt&gt; reste
identique.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-gestion-de-l-etat"&gt;
&lt;h2&gt;La gestion de l'état&lt;/h2&gt;
&lt;p&gt;Voilà le plus gros point d'achoppement à mon avis, la plus grosse différence
entre les langages fonctionnels (plus ou moins purs) et les langages objets&amp;nbsp;:
la gestion et le stockage d'un état changeant et les effets de bord.&lt;/p&gt;
&lt;p&gt;En programmation fonctionnelle, il n'y a pas de stockage d'un état changeant
dans les fonctions.  Une fonction retournera &lt;strong&gt;toujours&lt;/strong&gt; le même résultat pour
la même donnée en entrée.&lt;/p&gt;
&lt;p&gt;Une méthode d'un objet par contre pourra retourner un résultat différent selon
l'état stocké dans l'objet. Une méthode &lt;tt class="docutils literal"&gt;is_alive&lt;/tt&gt; sur un objet &lt;tt class="docutils literal"&gt;Cell&lt;/tt&gt;
retournera &lt;tt class="docutils literal"&gt;True&lt;/tt&gt; ou &lt;tt class="docutils literal"&gt;False&lt;/tt&gt; selon l'état de la cellule.&lt;/p&gt;
&lt;p&gt;L'avantage d'avoir un état changeant est de pouvoir justement cantonner des
morceaux de données dans différents objets, chacun avec ses responsabilités,
son domaine d'application. Avec un objet donné, on a toutes les informations
nécessaires à la gestion de cet objet, et on peut connaître à tout instant son
état actuel.&lt;/p&gt;
&lt;p&gt;Le stockage de l'état va souvent de pair avec les effets de bord. Une méthode
&lt;tt class="docutils literal"&gt;set_alive&lt;/tt&gt; sur un objet &lt;tt class="docutils literal"&gt;Cell&lt;/tt&gt; va par exemple passer cette cellule
vivante, mais aussi incrémenter son âge, ou encore incrémenter le compteur du
nombre de cellules vivantes de l'objet &lt;tt class="docutils literal"&gt;World&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Les inconvénients sont nombreux&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;les fixtures nécessaires pour l'écriture de tests (il faut toujours gérer
l'initialisation des objets dans un état connu)&lt;/li&gt;
&lt;li&gt;les effets de bord&amp;nbsp;: pas toujours connus, pas faciles à prévoir sans avoir
une connaissance parfaite de l'objet et du code&lt;/li&gt;
&lt;li&gt;une programmation concurrentielle très complexe&amp;nbsp;: il faut que chaque
&lt;em&gt;process&lt;/em&gt; soit au courant de l'état, qui doit donc être partagé/géré, ainsi
que les effets de bord&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="est-ce-qu-on-s-est-trompes"&gt;
&lt;h2&gt;Est-ce qu'on s'est trompés&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;Il est communément admis (en tout cas dans mon entourage) que l'utilisation
d'objets est plus intuitive, plus facile, plus claire et explicite. Seulement,
lors de la &lt;em&gt;code retreat&lt;/em&gt; et des différentes sessions, j'ai été confronté à des
visions très différentes de mes collègues de &lt;em&gt;pair-programming&lt;/em&gt;, par exemple
sur le découpage des objets, ou sur leur responsabilité&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;est-ce qu'il faut un objet &lt;tt class="docutils literal"&gt;CellContext&lt;/tt&gt; qui gère le contexte de la
cellule, ses voisines&lt;/li&gt;
&lt;li&gt;est-ce la responsabilité de la cellule de décider si elle doit vivre ou
mourir, ou plutôt celle du monde (ou de l'organisme, selon comment on
l'appelle)&amp;nbsp;? Comment gérer le cas de cellules prédatrices qui tueraient
d'autres cellules (ce n'est plus alors à la cellule elle-même de décider si
elle doit mourir).&lt;/li&gt;
&lt;li&gt;plus de code a écrire prends plus de temps, et pour des sessions de 45
minutes c'est très court, surtout en mode TDD (et on a vu que c'était plus
difficile et long d'écrire des tests pour des objets&amp;nbsp;: dans mon cas, 118
lignes de tests)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L'impression que ça m'a laissé est qu'avoir utilisé des objets nous mettait une
contrainte supplémentaire, un frein dont on aurait pu se passer.&lt;/p&gt;
&lt;p&gt;Alors oui, il y a de meilleures manières d'aboutir au même résultat. Oui, cet
exemple trivial n'est que peu représentatif de notre métier de développeur qui
est de se frotter à des problèmes beaucoup plus complexes.&lt;/p&gt;
&lt;p&gt;Oui, si il y a autant de monde qui fait de la POO, c'est vraisemblablement que
le concept n'est pas aberrant. Mais attention à la loi des nombres, ce n'est
pas parce que Java et PHP sont les langages les plus courants que je vais me
mettre à en (re)faire.&lt;/p&gt;
&lt;p&gt;Mais plus j'y pense, et plus je me dis qu'on s'est peut-être trompés. Pour les
curieux (et je vous recommande très fortement d'être curieux&amp;nbsp;!), voici quelques
liens à voir absolument&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;ce qu'aurait pu (dû&amp;nbsp;?) être la programmation&amp;nbsp;: &lt;a class="reference external" href="http://worrydream.com/dbx/"&gt;The future of programming&lt;/a&gt;
de Bret victor&lt;/li&gt;
&lt;li&gt;la programmation concurrentielle, on a plus le choix&amp;nbsp;: &lt;a class="reference external" href="http://www.infoq.com/presentations/erlang-software-for-a-concurrent-world"&gt;Erlang software for a
concurrent world&lt;/a&gt; de Joe Armstrong (créateur de Erlang)&lt;/li&gt;
&lt;li&gt;la simplicité dans la programmation&amp;nbsp;: &lt;a class="reference external" href="http://www.infoq.com/presentations/Simple-Made-Easy-QCon-London-2012"&gt;Simple made easy&lt;/a&gt; de Rich Hickey
(créateur de Clojure)&lt;/li&gt;
&lt;li&gt;pourquoi la programmation fonctionnelle&amp;nbsp;: &lt;a class="reference external" href="http://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf"&gt;Why functional programming matters&lt;/a&gt; de John Hughes (impliqué dans la création de Haskell)&lt;/li&gt;
&lt;li&gt;comment battre la concurrence avec Lisp&amp;nbsp;: &lt;a class="reference external" href="http://www.paulgraham.com/avg.html"&gt;Beating the averages&lt;/a&gt; de Paul
Graham (entrepreneur et capital risque)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Quitter Gmail : gestion des contacts</title><link href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html" rel="alternate"></link><published>2013-08-06T09:14:00+02:00</published><updated>2013-08-06T09:14:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-08-06:/blog/quitter-gmail-gestion-des-contacts.html</id><summary type="html">&lt;p&gt;Cet article est le cinquième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Gestion des contacts&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;La gestion des contacts est un peu plus complexe …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cet article est le cinquième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Gestion des contacts&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;La gestion des contacts est un peu plus complexe que ce que nous avons pu voir
jusqu'à présent.&lt;/p&gt;
&lt;p&gt;En effet, il n'y a malheureusement pas de moyen simple, pour le moment, d'avoir
les mêmes avantages qu'avec Google.&lt;/p&gt;
&lt;p&gt;Il existe tout de même plusieurs stratégies possibles, selon les cas
d'utilisation.&lt;/p&gt;
&lt;p&gt;Commençons par quelques définitions.&lt;/p&gt;
&lt;div class="section" id="termes-et-definitions"&gt;
&lt;h2&gt;Termes et définitions&lt;/h2&gt;
&lt;div class="section" id="vcard"&gt;
&lt;h3&gt;vCard&lt;/h3&gt;
&lt;p&gt;Le standard &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/VCard"&gt;vCard&lt;/a&gt; est un format d'échange de données personnelles, ce qu'on
appelle couramment les «&amp;nbsp;contacts&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;Pour exporter ses contacts Gmail dans ce format, et en faire ainsi une
sauvegarde, vous pouvez consulter la page d'aide &lt;a class="reference external" href="https://support.google.com/mail/answer/24911?hl=undefined"&gt;Exportation de contacts
Gmail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il faut surtout se rappeler d'exporter au format vCard, ce qui stockera tous
les contacts dans un fichier avec l'extension &lt;tt class="docutils literal"&gt;.vcf&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="carddav"&gt;
&lt;h3&gt;CardDav&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://fr.wikipedia.org/wiki/CardDAV"&gt;CardDav&lt;/a&gt; est un protocole d'échange de vCard, basé sur le modèle
client/serveur.&lt;/p&gt;
&lt;p&gt;Cela signifique que pour en profiter, il faut un serveur qui stocke les vCard
(les informations des contacts), et les clients peuvent ensuite s'y connecter
pour récuperer ces informations, ou les mettre à jour.&lt;/p&gt;
&lt;p&gt;On a alors une synchronisation des contacts sur les différents clients
(téléphone mobile, Thunderbird sur l'ordinateur...).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="comment"&gt;
&lt;h2&gt;Comment&lt;/h2&gt;
&lt;div class="section" id="rapatrier-ses-contacts"&gt;
&lt;h3&gt;Rapatrier ses contacts&lt;/h3&gt;
&lt;p&gt;Nous avons vu dans la définition de vCard, qu'il est possible d'exporter tous
les contacts de Gmail dans un fichier. Malheureusement, ce fichier n'est pas
bien importé par Thunderbird à l'heure actuelle.&lt;/p&gt;
&lt;p&gt;Pour information, une refonte complète de la partie gestion de contacts est en
cours pour Thunderbird&amp;nbsp;: &lt;a class="reference external" href="https://github.com/mikeconley/thunderbird-ensemble"&gt;Ensemble&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En attendant, pour récupérer proprement tous les contacts de Gmail sur
Thunderbird, il est recommandé d'utiliser le &lt;a class="reference external" href="https://support.mozillamessaging.com/fr/kb/faq-des-modules-complementaires"&gt;module complémentaire&lt;/a&gt;
Thunderbird &lt;a class="reference external" href="https://addons.mozilla.org/en-US/thunderbird/addon/google-contacts/"&gt;Google contacts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Une fois installé dans Thunderbird, il faut le configurer&amp;nbsp;: dans la gestion des
modules complémentaires, il y a un bouton à droite du module Google Contacts.&lt;/p&gt;
&lt;p&gt;Y rajouter son compte Gmail, est le reste se fera tout seul.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="exporter-importer-depuis-son-telephone-mobile"&gt;
&lt;h3&gt;Exporter/importer depuis son téléphone mobile&lt;/h3&gt;
&lt;p&gt;Voilà une méthode plus laborieuse, mais si les contacts sont rarements mis à
jour, c'est la solution la plus simple.&lt;/p&gt;
&lt;p&gt;Elle consiste à échanger des fichiers &lt;tt class="docutils literal"&gt;.vcf&lt;/tt&gt;, en exportant ses contacts d'une
source, et les important dans une autre.&lt;/p&gt;
&lt;div class="section" id="iphone"&gt;
&lt;h4&gt;iPhone&lt;/h4&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;importer&amp;nbsp;: envoyer le fichier &lt;tt class="docutils literal"&gt;.vcf&lt;/tt&gt; par mail, l'ouvrir sur son iPhone, et
importer tous les contacts&lt;/li&gt;
&lt;li&gt;exporter&amp;nbsp;: à ma connaissance un seul moyen si on utilise pas iTunes,
l'application &lt;a class="reference external" href="https://itunes.apple.com/en/app/my-contacts-backup/id446784593?mt=8"&gt;MyContactsBackup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="android"&gt;
&lt;h4&gt;Android&lt;/h4&gt;
&lt;p&gt;Envoyer le fichier sur le téléphone (par exemple par connexion USB, ou par
email puis sauvegarde dans le téléphone).&lt;/p&gt;
&lt;p&gt;Ensuite ouvrir le carnet d'adresse, puis le menu&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;importer&amp;nbsp;: choisir &lt;tt class="docutils literal"&gt;Importer/Exporter » Importer&lt;/tt&gt; depuis la mémoire&lt;/li&gt;
&lt;li&gt;exporter&amp;nbsp;: choisir &lt;tt class="docutils literal"&gt;Importer/Exporter » Exporter&lt;/tt&gt; vers la mémoire&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tout comme pour l'iPhone, il est possible d'ouvrir directement un fichier
&lt;tt class="docutils literal"&gt;.vcf&lt;/tt&gt; en pièce jointe d'un mail.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="thunderbird"&gt;
&lt;h4&gt;Thunderbird&lt;/h4&gt;
&lt;p&gt;Installer le module complémentaire &lt;a class="reference external" href="https://freeshell.de//~kaosmos/morecols-en.html"&gt;MoreFunctionsForAddressBook&lt;/a&gt; dans
Thunderbird. Dans une future version de Thunderbird, la gestion des vCard
devrait être plus aboutie, et ne plus nécessiter d'ajout de module
complémentaire.&lt;/p&gt;
&lt;p&gt;Ensuite, dans le carnet d'adresse, dans le menu &lt;tt class="docutils literal"&gt;Outil »
MoreFunctionsForAddressBook » Actions for contacts&lt;/tt&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;importer&amp;nbsp;: sélectionner &lt;tt class="docutils literal"&gt;Import vCard/vcf&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;exporter&amp;nbsp;: sélectionner &lt;tt class="docutils literal"&gt;Export » as vCard (.vcf)&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="synchronisation-directe"&gt;
&lt;h3&gt;Synchronisation directe&lt;/h3&gt;
&lt;p&gt;Elle consiste à connecter son téléphone directement avec l'ordinateur qui fait
tourner Thunderbird, et à lancer la synchronisation manuellement.&lt;/p&gt;
&lt;p&gt;L'avantage est que les contacts sur le téléphone mobile et sur Thunderbird sont
synchronisés dans les deux sens.&lt;/p&gt;
&lt;p&gt;L'inconvénient est qu'il faut passer par un logiciel annexe, ou par des modules
complémentaires pour Thunderbird, et une application sur son téléphone.&lt;/p&gt;
&lt;p&gt;N'ayant pu tester ces solutions, je ne peux les recommander, et elles me
paraissent peu fiables et difficiles d'utilisation. Voici néanmoins quelques
liens&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://sites.google.com/site/roger4apps/"&gt;Roger4apps&lt;/a&gt; : module complémentaire pour Thunderbird et application pour
téléphones Android&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.fjsoft.at/en/"&gt;MyPhoneExplorer&lt;/a&gt; : logiciel compatible windows et Android uniquement, qui ne
nécessite pas de module complémentaire Thunderbird&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="synchronisation-par-carddav"&gt;
&lt;h3&gt;Synchronisation par CardDav&lt;/h3&gt;
&lt;p&gt;C'est la solution la plus pratique et la plus aboutie, celle que vous utilisez
probablement déjà à l'heure actuelle.&lt;/p&gt;
&lt;p&gt;En effet, les iPhones synchronisent par défaut automatiquement vers iCloud, et
les Android vers Google, qui fournissent tous les deux un serveur CardDav.&lt;/p&gt;
&lt;p&gt;Voici quelques serveurs CardDav&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://owncloud.org"&gt;OwnCloud&lt;/a&gt; : il faut l'installer soi-même, ou &amp;quot;louer&amp;quot; une installation chez un
hébergeur&lt;/li&gt;
&lt;li&gt;iCloud: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;https://contacts.icloud.com&lt;/span&gt;&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://support.google.com/mail/answer/2753077?hl=fr"&gt;Google&lt;/a&gt;: ne fonctionne que pour iOS?&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="id3"&gt;
&lt;h4&gt;iPhone&lt;/h4&gt;
&lt;p&gt;Il est possible de synchroniser les contacts de son iPhone avec ses contacts
Google&amp;nbsp;: &lt;a class="reference external" href="https://support.google.com/mail/answer/2753077?hl=fr"&gt;Synchroniser les contacts avec votre appareil iOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On peut bien entendu utiliser cette même méthode avec n'importe quel serveur
CardDav.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h4&gt;Android&lt;/h4&gt;
&lt;p&gt;Il faut passer par une application tierce&amp;nbsp;: &lt;a class="reference external" href="https://play.google.com/store/apps/details?id=org.dmfs.carddav.sync&amp;amp;hl=en"&gt;CardDav-Sync&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id5"&gt;
&lt;h4&gt;Thunderbird&lt;/h4&gt;
&lt;p&gt;Là aussi, il faut passer par un module complémentaire&amp;nbsp;: &lt;a class="reference external" href="http://www.sogo.nu/english/downloads/frontends.html"&gt;SOGo Connector&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il existe un &lt;a class="reference external" href="http://pedia.zaclys.com/Synchronisation-des-contacts-entre-Thunderbird-et-Owncloud-avec-SOGo-connector,p54,276"&gt;tutoriel&lt;/a&gt; pour l'installer et l'utiliser avec OwnCloud, mais on
peut l'utiliser pour se connecter à n'importe quel serveur CardDav.&lt;/p&gt;
&lt;p&gt;D'après mes tests, par contre, le module complémentaire fonctionne de manière
aléatoire, et surtout, ne fonctionne pas du tout avec Google. Il est possible
que les décisions récentes de Google d'abandonner les standards ouverts
(CardDav, CalDav...) y soient pour quelque chose.&lt;/p&gt;
&lt;p&gt;Pour la synchronisation avec google, il faut donc se contenter du module
complémentaire indiqué dans le chapitre «&amp;nbsp;rapatrier ses contacts&amp;nbsp;».&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-suite"&gt;
&lt;h2&gt;La suite&lt;/h2&gt;
&lt;p&gt;Ma préférence personnelle va vers l'utilisation d'un serveur CardDav. C'est la
seule solution qui me paraît perenne, pratique, et qui égale la facilité de
synchronisation de contacts fournie par Google.&lt;/p&gt;
&lt;p&gt;Quel que soit le serveur CardDav utilisé, hormis si c'est sa propre
installation de OwnCloud ou équivalent, on confie et on donne accès à ses
contacts a l'hébergeur, ce qui ne fait que déplacer le problème.&lt;/p&gt;
&lt;p&gt;Néanmoins, l'avantage d'avoir ses contacts synchronisés partout est d'avoir
autant de sauvegardes.&lt;/p&gt;
&lt;p&gt;La solution la plus pratique à l'heure actuelle semble être &lt;a class="reference external" href="http://owncloud.org"&gt;OwnCloud&lt;/a&gt;, qu'il
est possible d'installer, par exemple, sur une petite &lt;a class="reference external" href="http://www.raspberrypi.org/"&gt;Raspberry-Pi&lt;/a&gt; qui
consomme très peu, et peut ainsi servir de serveur de sauvegarde personnel à la
maison pour un coût réduit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="quelques-projets-a-surveiller"&gt;
&lt;h2&gt;Quelques projets à surveiller&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://owncloud.org"&gt;OwnCloud&lt;/a&gt; : serveur CardDav (contacts), CalDav (calendrier), sauvegarde de
fichiers (à la Dropbox)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.mailpile.is/"&gt;mailpile&lt;/a&gt; : projet opensource visant à remplacer le client GMail&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.caliop.net/"&gt;caliop&lt;/a&gt; : projet naissant visant à fournir des outils et une plateform pour
les emails que les utilisateurs puissent utiliser avec confiance&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://radicale.org/"&gt;radicale&lt;/a&gt; : serveur CardDav et CalDav opensource&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://yunohost.org/"&gt;yunohost&lt;/a&gt; : distribution linux à installer sur un serveur, fournissant une
installation facile de OwnCloud, Jappix (réseau social), RoundCube (client
mail web)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/al3x/sovereign"&gt;sovereign&lt;/a&gt; : recettes permettant d'installer les logiciels nécessaires sur un
serveur pour s'héberger soi-même. Très complet, permet d'avoir son propre
serveur mail, son hébergement de site web, OwnCloud, VPN, sauvegarde de
fichiers...&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Quitter Gmail : migrer ses mails</title><link href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html" rel="alternate"></link><published>2013-08-05T19:07:00+02:00</published><updated>2013-08-05T19:07:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-08-05:/blog/quitter-gmail-migrer-ses-mails.html</id><summary type="html">&lt;p&gt;Cet article est le quatrième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Migrer ses mails&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour la plupart des gens, les mails sont stockés …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cet article est le quatrième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Migrer ses mails&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pour la plupart des gens, les mails sont stockés sur un serveur de mail, sur
une machine chez l'hébergeur.&lt;/p&gt;
&lt;p&gt;Changer d'hébergeur revient donc à perdre tous ses mails, à moins de les avoir
au préalable tous sauvegardés.&lt;/p&gt;
&lt;p&gt;Nous avons vu comment créer son compte mail, mais n'avons pas encore parlé de
la migration des mails vers ce nouvel hébergeur, nécessaire à la stratégie
courageuse de l'article précédent.&lt;/p&gt;
&lt;p&gt;Commençons par quelques définitions.&lt;/p&gt;
&lt;div class="section" id="termes-et-definitions"&gt;
&lt;h2&gt;Termes et définitions&lt;/h2&gt;
&lt;div class="section" id="imap"&gt;
&lt;h3&gt;IMAP&lt;/h3&gt;
&lt;p&gt;Le protocole
&lt;abbr title="Internet Message Access Protocol&amp;nbsp;: protocole d'accès aux messages internet"&gt;IMAP&lt;/abbr&gt;
permet de consulter ses mails, tout en les laissant sur le serveur, chez
l'hébergeur.&lt;/p&gt;
&lt;p&gt;L'avantage est double&amp;nbsp;: pour commencer, le message n'est téléchargé qu'au
moment de la visualisation, économisant de la bande passante et de l'espace
disque (imaginez la réception d'un mail énorme sur un téléphone portable à
l'espace limité).&lt;/p&gt;
&lt;p&gt;Ensuite, vu que les messages restent sur le serveur, ils sont visualisables
depuis n'importe quel appareil à tout moment.&lt;/p&gt;
&lt;p&gt;L'inconvénient étant que les messages, stockés sur le serveur, doivent être
migrés lors d'un changement d'hébergeur.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="comment"&gt;
&lt;h2&gt;Comment&lt;/h2&gt;
&lt;p&gt;Pour migrer ses mails, Jean va devoir commencer par rapatrier tous ses mails
sur son ordinateur. Et pour ça, il va être obligé d'utiliser un client lourd
(cf l'article précédent).&lt;/p&gt;
&lt;p&gt;Nous allons prendre l'exemple de Thunderbird.&lt;/p&gt;
&lt;div class="section" id="connecter-thunderbird-a-gmail"&gt;
&lt;h3&gt;Connecter Thunderbird à Gmail&lt;/h3&gt;
&lt;p&gt;C'est la partie la plus simple&amp;nbsp;: il suffit de &lt;a class="reference external" href="https://support.mozillamessaging.com/fr/kb/configuration-automatique-de-compte"&gt;créer un nouveau compte&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configurer-la-synchronisation"&gt;
&lt;h3&gt;Configurer la synchronisation&lt;/h3&gt;
&lt;p&gt;Il faut ensuite être sûr que le compte nouvellement créé est configuré pour que
tous les messages soient conservés sur l'ordinateur.&lt;/p&gt;
&lt;p&gt;Pour cela, cocher la case «&amp;nbsp;Conserver les messages de ce compte sur cet
ordinateur&amp;nbsp;», comme indiqué sur la page d'aide &lt;a class="reference external" href="https://support.mozillamessaging.com/fr/kb/le-protocole-imap#w_configurer-la-synchronisation-et-laoespace-disque"&gt;configurer la synchronisation
et l'espace disque&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rapatrier-les-mails"&gt;
&lt;h3&gt;Rapatrier les mails&lt;/h3&gt;
&lt;p&gt;Il ne reste plus qu'à passer Thunderbird en mode «&amp;nbsp;hors ligne&amp;nbsp;», pour forcer la
synchronisation, ce qui dans notre cas correspond au téléchargement de tous les
mails de Gmail.&lt;/p&gt;
&lt;p&gt;Le passage en mode hors ligne se fait en cliquant sur la petite icône, tout en
bas à gauche de la fenêtre de Thunderbird, dans la barre de statut, ou par le
menu &lt;tt class="docutils literal"&gt;Fichier » Hors Ligne » Travailler hors ligne&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Selon le nombre de mails, et leur taille, cela peut prendre un long moment.
Pour information, il a fallut pas loin d'une heure pour télécharger un peu plus
de 5000 mails, pour une taille totale de 2,23Go, avec une bonne connexion
internet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="transferer-sur-le-nouvel-hebergeur"&gt;
&lt;h3&gt;Transférer sur le nouvel hébergeur&lt;/h3&gt;
&lt;p&gt;Maintenant que tous les messages sont téléchargés, il ne reste plus qu'à faire
un gros copier-coller de tous les messages du compte Gmail vers le compte sur
le nouvel hébergeur.&lt;/p&gt;
&lt;p&gt;Attention, cette manipulation prendra beaucoup plus longtemps que le
téléchargement des mails. En effet, les connexions internet de nos jours sont
assymétriques&amp;nbsp;: on télécharge beaucoup plus vite qu'on envoie.&lt;/p&gt;
&lt;p&gt;Une fois l'envoi de tous les mails terminés, ils seront accessibles directement
par le biais du nouveau compte créé à l'article précédent, celui sur le nouvel
hébergeur.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-suite"&gt;
&lt;h2&gt;La suite&lt;/h2&gt;
&lt;p&gt;Si Jean utilise Thunderbird au quotidien, il aura tous ses mails synchronisés
entre l'hébergeur et son ordinateur, et donc&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;une copie de ses mails sur l'hébergeur&lt;/li&gt;
&lt;li&gt;une copie de ses mails sur son ordinateur&lt;/li&gt;
&lt;li&gt;une copie de ses mails sauvegardés par ses soins&lt;/li&gt;
&lt;li&gt;une étape de moins à exécuter (le rapatriement) lors du prochain changement
d'hébergeur&lt;/li&gt;
&lt;li&gt;la possibilité de travailler hors ligne&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Vous DEVEZ avoir une sauvegarde de tous vos fichiers, pas seulement
de vos mails&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Le prochain article abordera la &lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Quitter Gmail : créer son compte mail</title><link href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html" rel="alternate"></link><published>2013-07-31T08:21:00+02:00</published><updated>2013-07-31T08:21:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-07-31:/blog/quitter-gmail-creer-son-compte-mail.html</id><summary type="html">&lt;p&gt;Cet article est le troisième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Créer son compte mail&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans cet article nous allons étudier les différentes possibilités …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cet article est le troisième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Créer son compte mail&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans cet article nous allons étudier les différentes possibilités et stratégies
pour se passer de Gmail pour son adresse mail.&lt;/p&gt;
&lt;p&gt;Pour rappel, Jean Dupont, qui utilisait jusqu'à présent son adresse
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;, vient de se procurer un compte mail chez son
hébergeur, ainsi qu'un nom de domaine et une adresse &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; qui
pointent vers cet hébergeur.&lt;/p&gt;
&lt;p&gt;Commençons par quelques définitions.&lt;/p&gt;
&lt;div class="section" id="termes-et-definitions"&gt;
&lt;h2&gt;Termes et définitions&lt;/h2&gt;
&lt;div class="section" id="adresse-mail"&gt;
&lt;h3&gt;Adresse mail&lt;/h3&gt;
&lt;p&gt;C'est l'équivalent du numéro de téléphone. C'est ce qui permet d'acheminer les
messages au bon endroit.&lt;/p&gt;
&lt;p&gt;Il faut donc voir l'adresse mail comme une localisation qui peut changer (le
même numéro pourrait être chez SFR un jour, et chez Bouygues le lendemain).&lt;/p&gt;
&lt;p&gt;Une adresse email est composée de deux éléments&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;em&gt;jean&lt;/em&gt; : la partie précédent le &lt;em&gt;&amp;#64;&lt;/em&gt;, la partie variable, qui correspond au
compte mail sur le serveur mail&lt;/li&gt;
&lt;li&gt;&lt;em&gt;dupont.fr&lt;/em&gt; : la partie après le &lt;em&gt;&amp;#64;&lt;/em&gt;, qui est le nom de domaine (se référer à
l'article précédent), et qui pointe vers un serveur mail&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si Jean le souhaite, il peut modifier &lt;em&gt;dupont.fr&lt;/em&gt; pour qu'il pointe sur un
autre serveur mail et y créer un nouveau compte, contrairement à l'adresse
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt; qui appartient à Google, et qui ne pointera jamais
ailleurs que sur les serveurs de Google.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="compte-mail"&gt;
&lt;h3&gt;Compte mail&lt;/h3&gt;
&lt;p&gt;C'est l'équivalent de la boite postale dans lequel le facteur dépose votre
courrier. C'est l'endroit où arrivent tous les messages.&lt;/p&gt;
&lt;p&gt;Contrairement à la boite postale qui ne sert que temporairement, le temps de
récupérer le courrier, un compte mail peut stocker les messages aussi longtemps
qu'on le souhaite.&lt;/p&gt;
&lt;p&gt;Ce compte est localisé sur un serveur, une machine. En reprenant l'exemple de
Gmail, le compte est localisé sur une machine «&amp;nbsp;chez Google&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;À la manière d'un dossier sur un ordinateur, un compte stocke tous les messages
reçus, envoyés, supprimés, classés dans des sous-dossiers, déplacés dans les
spams...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="client-mail"&gt;
&lt;h3&gt;Client mail&lt;/h3&gt;
&lt;p&gt;Le programme qui permet de&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;lister les messages&lt;/li&gt;
&lt;li&gt;lire les messages&lt;/li&gt;
&lt;li&gt;écrire des messages&lt;/li&gt;
&lt;li&gt;trier, ordonner, classer les messages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Accessoirement, un client permet aussi de gérer les adresses (contacts), mais
c'est un autre sujet que nous aborderons dans un futur article de cette série.&lt;/p&gt;
&lt;p&gt;Il existe deux grands types de clients&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;le client web&amp;nbsp;: un site, comme par exemple &lt;a class="reference external" href="http://mail.google.com"&gt;http://mail.google.com&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;s'utilise sur le port 80, qui est rarement bloqué (dans les hôtels, sur les
accès wifi publics, dans les entreprises...)&lt;/li&gt;
&lt;li&gt;ne nécessite pas d'installation de logiciel sur son ordinateur&lt;/li&gt;
&lt;li&gt;ne nécessite pas de configuration (dans le cas où il est fourni directement
par l'hébergeur du compte utilisé)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;le client lourd&amp;nbsp;: un programme à installer sur son ordinateur, comme par
exemple &lt;a class="reference external" href="http://www.mozilla.org/fr/thunderbird/?flang=fr"&gt;Thunderbird&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;sauvegarde les messages sur l'ordinateur&lt;/li&gt;
&lt;li&gt;plus pratique et ergonomique qu'un client web&lt;/li&gt;
&lt;li&gt;peut être utilisé hors connexion&amp;nbsp;: les messages à envoyer sont stockés puis
envoyés lors de la prochaine connexion&lt;/li&gt;
&lt;li&gt;n'est pas dépendant d'un hébergeur, et donc tous les mails seront conservés
le jour d'un changement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans une certaine limite, les clients sont interchangeables. Ainsi, on peut
utiliser le &lt;strong&gt;client&lt;/strong&gt; Thunderbird pour accéder à un &lt;strong&gt;compte&lt;/strong&gt; Gmail.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="redirection"&gt;
&lt;h3&gt;Redirection&lt;/h3&gt;
&lt;p&gt;Comme un renvoi d'appel ou le suivi de courrier postal&amp;nbsp;: il permet de faire
suivre tout le courrier normalement destiné à une adresse vers une autre
adresse.&lt;/p&gt;
&lt;p&gt;Ainsi, Jean peut décider de rediriger tous les messages destinés à
&lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; vers l'adresse &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sauvegarde"&gt;
&lt;h3&gt;Sauvegarde&lt;/h3&gt;
&lt;p&gt;Il est possible de conserver plusieurs copies de ses messages, afin d'avoir une
sauvegarde en cas de défaillance d'une machine (son ordinateur, la machine de
son hébergeur...).&lt;/p&gt;
&lt;p&gt;Exemple&amp;nbsp;: l'utilisation d'un client lourd comme Thunderbird permet d'avoir une
copie des messages sur son ordinateur, tout en les conservant sur le serveur
(sur le compte). On peut alors envisager de sauvegarder ces messages (qui sont
stockés sous forme de fichiers sur l'ordinateur) sur un disque USB, un
&lt;abbr title="Network-Attached Storage&amp;nbsp;: disque dur réseau"&gt;NAS&lt;/abbr&gt;...&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="comment"&gt;
&lt;h2&gt;Comment&lt;/h2&gt;
&lt;p&gt;Jean veut avoir le contrôle de la destination de ses messages, afin de pouvoir
changer d'avis si il le souhaite. Il va donc faire passer le mot que sa
nouvelle adresse est désormais &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;, et non plus
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Par contre, Jean veut toujours recevoir les mails envoyés à
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;, car il y a beaucoup d'entités qui ne connaissent pas
encore sa nouvelle adresse, comme les impôts, EDF, ou encore des abonnements à
des listes de diffusion...&lt;/p&gt;
&lt;p&gt;Avant de rentrer dans le détail, voici les deux stratégies proposées&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;timorée&amp;nbsp;: conserver Gmail comme compte principal&lt;/li&gt;
&lt;li&gt;courageuse&amp;nbsp;: utiliser son nouveau compte comme compte principal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ces deux étapes sont indépendantes, et il est tout à fait possible de rester à
la première étape, ou encore de passer directement à la deuxième étape.&lt;/p&gt;
&lt;p&gt;Le plus important est de pouvoir utiliser sa nouvelle adresse mail, afin
d'avoir à minima le contrôle sur la destination des messages.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="strategie-timoree-conserver-gmail-comme-compte-principal"&gt;
&lt;h2&gt;Stratégie timorée&amp;nbsp;: Conserver Gmail comme compte principal&lt;/h2&gt;
&lt;p&gt;Cette stratégie est un compromis qui permet de ne pas changer grand chose à ses
habitudes quotidiennes, en continuant à utiliser le client Gmail.&lt;/p&gt;
&lt;p&gt;L'inconvénient est que Google a toujours accès à tous les messages, et cette
stratégie demande plus de configuration.&lt;/p&gt;
&lt;div class="section" id="rediriger-jean-dupont-fr-vers-jean-dupont-gmail-com"&gt;
&lt;h3&gt;Rediriger &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; vers &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;C'est la toute première chose à faire. Sur son hébergeur, Jean va configurer
son adresse &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; pour qu'elle redirige tous les messages vers
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Ainsi, dès que quelqu'un écrira à &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;, le message sera
automatiquement transféré, relayé, redirigé vers &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt; (comme
si il avait été destiné à &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt; dès le début).&lt;/p&gt;
&lt;p&gt;Il accédera alors à ses messages toujours de la même manière, en se connectant
sur &lt;a class="reference external" href="http://mail.google.com"&gt;http://mail.google.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cette redirection devra rester en place tant que la stratégie courageuse ne
sera pas mise en place.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configurer-le-client-gmail-envoyer-les-mails-de-la-part-de-jean-dupont-fr"&gt;
&lt;h3&gt;Configurer le client Gmail&amp;nbsp;: envoyer les mails de la part de &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Par défaut, un client mail envoie tous les mails de la part de l'adresse mail
associée au compte sur lequel le client se connecte.&lt;/p&gt;
&lt;p&gt;Ainsi, le client Gmail va automatiquement envoyer tous les mails de la part de
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Prenons le scénario suivant&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;em&gt;bill&amp;#64;smith.com&lt;/em&gt; envoie un mail à &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;le mail arrive sur l'hébergeur de Jean, qui redirige le message vers
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;le mail arrive chez Google sur son compte&lt;/li&gt;
&lt;li&gt;Jean consulte le message et y répond&lt;/li&gt;
&lt;li&gt;le client Gmail envoie la réponse de la part de &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;et là Bill répondra à l'adresse Gmail, au lieu de l'adresse &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jean aura beau eu faire part de sa nouvelle adresse, dans les faits, la plupart
des messages continueront à être directement envoyés à son adresse Gmail.&lt;/p&gt;
&lt;p&gt;Il lui faut donc configurer son client Gmail pour qu'il envoie tous les mails
de la part de &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Il y a une page expliquant comment faire cela&amp;nbsp;: &lt;a class="reference external" href="https://support.google.com/mail/answer/22370?hl=fr&amp;amp;ctx=mail"&gt;Envoi de message avec une
autre adresse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Voici une explication résumée (si vous utilisez Alwaysdata, reportez-vous en
fin de cette article pour des captures d'écran explicatives)&amp;nbsp;:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Cliquez sur l'icône représentant une roue dentée en haut à droite de
l'écran, puis sélectionnez Paramètres&lt;/li&gt;
&lt;li&gt;Cliquez sur l'onglet Comptes&lt;/li&gt;
&lt;li&gt;Sous «&amp;nbsp;Envoyer des e-mails en tant que&amp;nbsp;», cliquez sur «&amp;nbsp;Ajouter une autre
adresse e-mail&amp;nbsp;»&lt;/li&gt;
&lt;li&gt;Dans le champ «&amp;nbsp;Adresse e-mail&amp;nbsp;», saisissez votre nom (Jean Dupont) et
l'autre adresse e-mail (&lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;), et décochez la case «&amp;nbsp;Traiter
comme un alias&amp;nbsp;»&lt;/li&gt;
&lt;li&gt;Choisissez l'option «&amp;nbsp;Utiliser les serveurs SMTP de votre autre fournisseur de messagerie&amp;nbsp;»&lt;/li&gt;
&lt;li&gt;Entrez les informations de connexion au compte de votre hébergeur&lt;/li&gt;
&lt;li&gt;Cliquez sur «&amp;nbsp;Enregistrer les modifications&amp;nbsp;»&lt;/li&gt;
&lt;li&gt;De retour dans les paramètres du compte, cliquez sur le lien «&amp;nbsp;utiliser par
défaut&amp;nbsp;» à droite de la nouvelle adresse que vous venez de créer&lt;/li&gt;
&lt;li&gt;Choisissez enfin, sous «&amp;nbsp;En réponse à un message&amp;nbsp;», l'option «&amp;nbsp;Toujours
répondre à partir de l'adresse par défaut (actuellement &lt;a class="reference external" href="mailto:jean&amp;#64;dupont.fr"&gt;jean&amp;#64;dupont.fr&lt;/a&gt;)&amp;nbsp;»&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Suite à ce changement, tous les mails qui seront envoyés à partir du client
Gmail seront envoyés de la part de &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;, et donc toutes les
personnes qui répondent, répondront directement à cette nouvelle adresse mail.&lt;/p&gt;
&lt;p&gt;Tous les mails envoyés à &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; ou à &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;
arriverons sur son compte Gmail.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="strategie-courageuse-utiliser-son-nouveau-compte"&gt;
&lt;h2&gt;Stratégie courageuse&amp;nbsp;: Utiliser son nouveau compte&lt;/h2&gt;
&lt;p&gt;Bien qu'il soit théoriquement possible de continuer à utiliser le client Gmail,
en le connectant sur le compte de l'hébergeur, dans la pratique ce n'est pas
vraiment possible pour des raisons techniques (pour les curieux, le client
Gmail ne permet pas de se connecter à un compte externe en IMAP, mais
uniquement en POP, ce qui revient à utiliser le compte Gmail, chez Google
donc).&lt;/p&gt;
&lt;p&gt;Il va donc falloir que Jean utilise un autre client mail, comme
par exemple Thunderbird. Il lui faudra le télécharger, l'installer, et le
configurer (voir en fin d'article l'exemple de l'hébergement chez Alwaysdata).&lt;/p&gt;
&lt;p&gt;Il peut autrement préférer utiliser le «&amp;nbsp;webmail&amp;nbsp;» fourni par son hébergeur
(par exemple Roundcube, qui est assez répandu), pour continuer à consulter ses
messages directement sur un site internet, sans avoir à installer de logiciel
sur son ordinateur.&lt;/p&gt;
&lt;p&gt;Afin de continuer à recevoir les mails envoyés à &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;, il
va falloir qu'il configure une redirection au niveau de Gmail.&lt;/p&gt;
&lt;div class="section" id="rediriger-jean-dupont-gmail-com-vers-jean-dupont-fr"&gt;
&lt;h3&gt;Rediriger &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt; vers &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;Cette redirection se met en place par le biais du client Gmail, et est bien
expliquée sur le site du support de Google&amp;nbsp;: &lt;a class="reference external" href="https://support.google.com/mail/answer/10957?hl=fr&amp;amp;ctx=mail"&gt;Transfert automatique des
messages vers un autre compte de messagerie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ATTENTION&amp;nbsp;:&lt;/strong&gt; si vous aviez au préalable mis en place une redirection vers
l'adresse Gmail, il vous faut à présent impérativement la désactiver. Ainsi,
Jean devra désactiver la redirection des mails de &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; vers
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Une fois la redirection mise en place sur son adresse &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;,
Jean pourra utiliser son nouveau client pour se connecter à son compte chez son
hébergeur.&lt;/p&gt;
&lt;p&gt;Tous les mails envoyés à &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; ou à &lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;
arriverons sur son compte chez son hébergeur.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Et demain&amp;nbsp;? Si jamais Jean décide de changer d'hébergeur&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Il lui suffira de configurer son nom de domaine pour qu'il pointe vers le
serveur de son nouvel hébergeur (enregistrements &lt;em&gt;MX&lt;/em&gt;, se reporter à l'article
précédent), puis qu'il y crée un compte pour son adresse mail.&lt;/p&gt;
&lt;p&gt;Il lui faudra aussi configurer son client lourd pour qu'il pointe sur le
nouveau compte, ou utiliser le client web fourni par son nouvel hébergeur.&lt;/p&gt;
&lt;p&gt;Il n'y aura plus à créer de redirection ou à configurer une adresse
d'expédition, bref, plus de soucis, tout est sous son contrôle, et aucun besoin
de contacter tout son carnet d'adresse pour faire connaître sa nouvelle
adresse.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="informations-de-connexion-a-un-compte-heberge-par-alwaysdata"&gt;
&lt;h2&gt;Informations de connexion à un compte hébergé par Alwaysdata&lt;/h2&gt;
&lt;div class="section" id="strategie-timoree"&gt;
&lt;h3&gt;Stratégie timorée&lt;/h3&gt;
&lt;p&gt;Voici comment configurer le client Gmail pour envoyer les mails de la part de
&lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt; (stratégie timorée)&amp;nbsp;:&lt;/p&gt;
&lt;img alt="Configuration de Gmail pour l'hébergeur Alwaysdata (1)" src="//mathieu.agopian.info/blog/images/gmail_alwaysdata_1.png" /&gt;
&lt;img alt="Configuration de Gmail pour l'hébergeur Alwaysdata (2)" src="//mathieu.agopian.info/blog/images/gmail_alwaysdata_2.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="strategie-courageuse"&gt;
&lt;h3&gt;Stratégie courageuse&lt;/h3&gt;
&lt;p&gt;Voici à quoi ressemble la configuration lors de l'ajout d'un compte mail sur
Thunderbird&amp;nbsp;:&lt;/p&gt;
&lt;img alt="Configuration de Thunderbird pour l'hébergeur Alwaysdata" src="//mathieu.agopian.info/blog/images/thunderbird_alwaysdata.png" /&gt;
&lt;p&gt;Alwaysdata fourni aussi un client web (Roundcube) accessible sur
&lt;a class="reference external" href="https://webmail.alwaysdata.com"&gt;https://webmail.alwaysdata.com&lt;/a&gt;. Il suffit alors d'indiquer son mail et son mot
de passe, aucune autre configuration n'est requise.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-suite"&gt;
&lt;h2&gt;La suite&lt;/h2&gt;
&lt;p&gt;Le prochain article donnera des techniques pour &lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Quitter Gmail : réserver son nom de domaine</title><link href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html" rel="alternate"></link><published>2013-07-30T13:44:00+02:00</published><updated>2013-07-30T13:44:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-07-30:/blog/quitter-gmail-reserver-son-nom-de-domaine.html</id><summary type="html">&lt;p&gt;Cet article est le deuxième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Réserver son propre nom de domaine&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nous allons voir dans un prochain article comment créer …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cet article est le deuxième d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail.html"&gt;Pourquoi quitter Gmail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Réserver son propre nom de domaine&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nous allons voir dans un prochain article comment créer son compte mail, mais
cela nécessite d'avoir au préalable son propre nom de domaine.&lt;/p&gt;
&lt;p&gt;Commençons par quelques définitions.&lt;/p&gt;
&lt;div class="section" id="termes-et-definitions"&gt;
&lt;h2&gt;Termes et définitions&lt;/h2&gt;
&lt;div class="section" id="hebergeur"&gt;
&lt;h3&gt;Hébergeur&lt;/h3&gt;
&lt;p&gt;Un hébergeur s'occupe d'une machine pour vous. Pour qu'une machine (un serveur)
soit accessible à d'autres, pour qu'ils puissent aller sur votre site internet,
vous envoyer un mail... il faut que cette machine soit&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;correctement configurée&lt;/li&gt;
&lt;li&gt;toujours allumée&lt;/li&gt;
&lt;li&gt;administrée au quotidien&amp;nbsp;: mises à jour de sécurité, installation de
nouvelles version...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vous pouvez tout à fait vous en occuper vous-même, si vous en avez les
compétences, les ressources et le temps nécessaire à y consacrer. Pour la
plupart des gens, ce n'est pas le cas, et un hébergeur le fait pour vous.&lt;/p&gt;
&lt;p&gt;Un hébergeur va donc vous louer une machine (ou une partie de cette machine),
avec différents services&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;hébergement de site internet&lt;/li&gt;
&lt;li&gt;base de données&lt;/li&gt;
&lt;li&gt;stockage de fichiers&lt;/li&gt;
&lt;li&gt;compte mail (c'est ce qui nous intéresse plus particulièrement dans le cadre
de cette série d'articles)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Certains proposent des packs, comme par exemple &lt;a class="reference external" href="https://alwaysdata.com"&gt;Alwaysdata&lt;/a&gt; qui propose pour
moins de 10€ par mois autant de comptes mails qu'on le souhaite, des bases de
données, du stockage de fichiers, de l'hébergement de site web...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="nom-de-domaine"&gt;
&lt;h3&gt;Nom de domaine&lt;/h3&gt;
&lt;p&gt;On peut voir les noms de domaine comme une entrée dans un annuaire, qui fait
correspondre un nom à un numéro de téléphone ou une adresse.&lt;/p&gt;
&lt;p&gt;Un nom de domaine s'achète auprès d'un
&lt;abbr title="Organisme chargé de l'attribution de noms de domaines"&gt;registrar&lt;/abbr&gt;
(comme &lt;a class="reference external" href="http://bookmyname.com"&gt;BookMyName&lt;/a&gt; par exemple) ou d'un intermédiaire, comme un hébergeur qui
s'en occupe pour vous.&lt;/p&gt;
&lt;p&gt;Selon les extensions (la dernière partie d'un nom de domaine, comme par exemple
&lt;em&gt;.fr&lt;/em&gt; pour &lt;em&gt;dupont.fr&lt;/em&gt;), et selon le &lt;em&gt;registrar&lt;/em&gt; ou l'intermédiaire, un nom de
domaine coûtera entre 5€ et 15€ par an en moyenne.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="comment"&gt;
&lt;h2&gt;Comment&lt;/h2&gt;
&lt;p&gt;Passer directement par un &lt;em&gt;registrar&lt;/em&gt; est moins cher mais nécessite une
configuration par le biais de son interface d'administration pour indiquer qui
(quel serveur) va s'occuper techniquement de vos emails.&lt;/p&gt;
&lt;p&gt;L'intérêt de passer par le biais d'un hébergeur est qu'il n'y a pas besoin de
se soucier de cette configuration, et que la gestion centralisée de ce nom de
domaine et du compte mail est plus pratique.&lt;/p&gt;
&lt;p&gt;Dans tous les cas, le nom de domaine acheté vous est réservé, et vous le faites
pointer vers où vous voulez tant qu'il vous est réservé. Vous pouvez par
ailleurs décider de transférer la gestion de ce nom de domaine à un autre si
vous le souhaitez&amp;nbsp;: Par exemple, Jean peut avoir acheté son domaine chez
&lt;a class="reference external" href="http://bookmyname.com"&gt;BookMyName&lt;/a&gt; (registrar), et l'avoir transféré par la suite chez &lt;a class="reference external" href="https://alwaysdata.com"&gt;Alwaysdata&lt;/a&gt;
(hébergeur).&lt;/p&gt;
&lt;div class="section" id="trouver-un-nom-de-domaine"&gt;
&lt;h3&gt;Trouver un nom de domaine&lt;/h3&gt;
&lt;p&gt;La plupart des &lt;em&gt;registrar&lt;/em&gt; et des intermédiaires mettent à disposition un outil
de recherche sur le nom de domaine et son extension.&lt;/p&gt;
&lt;p&gt;Ainsi, Jean pourrait faire une recherche sur &amp;quot;dupont&amp;quot;, et voir qu'il y a
plusieurs domaines libres&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;dupont.fr&lt;/li&gt;
&lt;li&gt;dupont.org&lt;/li&gt;
&lt;li&gt;dupont.net&lt;/li&gt;
&lt;li&gt;dupont.info&lt;/li&gt;
&lt;li&gt;dupont.com&lt;/li&gt;
&lt;li&gt;ou encore d'autres...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il peut alors en choisir un, ou plusieurs si il le souhaite. Dans ce dernier
cas, il pourra décider de tous les faire pointer vers le même endroit.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configurer-un-nom-de-domaine"&gt;
&lt;h3&gt;Configurer un nom de domaine&lt;/h3&gt;
&lt;p&gt;Cette étape n'est nécessaire que lorsque le nom de domaine a été réservé par un
autre biais que l'hébergeur lui-même.&lt;/p&gt;
&lt;p&gt;Voici l'exemple de Jean Dupont qui veut faire pointer son nom de domaine
&lt;em&gt;dupont.fr&lt;/em&gt; acheté chez BookMyName vers son hébergeur Alwaysdata. Au niveau de
l'interface d'administration de BookMyName, il doit rajouter les
enregistrements suivants, spécifiques à la gestion des mails (pour un
hébergement de site web ce sera différent)&amp;nbsp;:&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="21%" /&gt;
&lt;col width="10%" /&gt;
&lt;col width="7%" /&gt;
&lt;col width="19%" /&gt;
&lt;col width="43%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;domain&lt;/th&gt;
&lt;th class="head"&gt;Type&lt;/th&gt;
&lt;th class="head"&gt;TTL&lt;/th&gt;
&lt;th class="head"&gt;Priority&lt;/th&gt;
&lt;th class="head"&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;dupont.fr&lt;/td&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;mx1.alwaysdata.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;dupont.fr&lt;/td&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;mx2.alwaysdata.com&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Il faut ensuite se connecter à son compte Alwaysdata, &lt;a class="reference external" href="http://wiki.alwaysdata.com/wiki/Ajouter_un_domaine"&gt;rajouter le nom de
domaine&lt;/a&gt;, et créer son adresse mail (par exemple &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-suite"&gt;
&lt;h2&gt;La suite&lt;/h2&gt;
&lt;p&gt;Et demain&amp;nbsp;? Si jamais Jean décide de changer d'hébergeur, de &lt;em&gt;registrar&lt;/em&gt;, de
serveur de mail&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Il n'aura plus aucun soucis&amp;nbsp;: il lui suffira de changer la configuration de son
nom de domaine pour pointer vers son nouvel hébergeur, vers son nouveau compte
mail... Et cela sans impacter qui que ce soit.&lt;/p&gt;
&lt;p&gt;Le prochain article expliquera comment &lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Je n'ai aucun intéressement chez &lt;a class="reference external" href="https://alwaysdata.com"&gt;Alwaysdata&lt;/a&gt; ni chez &lt;a class="reference external" href="http://bookmyname.com"&gt;BookMyName&lt;/a&gt;, si
je les prend en exemple c'est que je suis (ou ai été, dans le cas de
BookMyName) un client satisfait.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Quitter Gmail</title><link href="//mathieu.agopian.info/blog/quitter-gmail.html" rel="alternate"></link><published>2013-07-30T08:38:00+02:00</published><updated>2013-07-30T08:38:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-07-30:/blog/quitter-gmail.html</id><summary type="html">&lt;p&gt;Cet article est le premier d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Pourquoi quitter Gmail&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Les exemples donnés dans cette série d'articles se basent …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cet article est le premier d'une série sur comment reprendre le contrôle sur
son mail, et quitter son fournisseur de mail centralisé.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Pourquoi quitter Gmail&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-creer-son-compte-mail.html"&gt;Créer son compte mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-migrer-ses-mails.html"&gt;Migrer ses mails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-gestion-des-contacts.html"&gt;Gestion des contacts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Les exemples donnés dans cette série d'articles se basent sur Gmail, mais ils
sont très largement applicables pour d'autres fournisseurs comme LaPoste.net ou
Hotmail.&lt;/p&gt;
&lt;p&gt;Cette série est destinée aux personnes n'ayant pas ou peu de connaissances
techniques du domaine, et a pour but de permettre à chacun reprendre le
contrôle sur une partie de son identité numérique, celle qui a trait à son
adresse mail.&lt;/p&gt;
&lt;p&gt;Nous allons apprendre ce qu'est un nom de domaine, expliquer la différence
entre une adresse mail, un compte mail et un client mail, et donner des
stratégies pour se passer de Gmail.&lt;/p&gt;
&lt;p&gt;Pour l'histoire, Jean Dupont, qui utilisait jusqu'à présent son adresse
&lt;em&gt;jean.dupont&amp;#64;gmail.com&lt;/em&gt;, veut dorénavant pouvoir gérer ses mails sans dépendre
de Google, et va mettre en place sa propre adresse mail &lt;em&gt;jean&amp;#64;dupont.fr&lt;/em&gt;.&lt;/p&gt;
&lt;div class="section" id="quitter-gmail-pourquoi"&gt;
&lt;h2&gt;Quitter Gmail&amp;nbsp;: Pourquoi&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;À l'heure du &lt;em&gt;cloud&lt;/em&gt;, du mail facile et du Gmail si pratique et quasi
universel, pourquoi donc vouloir s'en passer&amp;nbsp;?&lt;/p&gt;
&lt;div class="section" id="controler-ses-donnees"&gt;
&lt;h3&gt;Contrôler ses données&lt;/h3&gt;
&lt;p&gt;De nos jours, il est très facile de se passer de son identité numérique. Ou
plutôt, il est très difficile d'en garder le contrôle. Il devient monnaie
courante d'abandonner nos données à des services tiers comme Google ou
Facebook.  On se construit alors son identité sur ces réseaux, sur ces
«&amp;nbsp;silos&amp;nbsp;» à données.&lt;/p&gt;
&lt;p&gt;Il ne faut pas se leurrer, ce ne sont alors plus nos données&amp;nbsp;: notre identité,
tout leur appartient, et tout leur est utile à des fins commerciales. Ces
services vont ainsi pouvoir revendre vos données, ou s'en servir pour vous
présenter des publicités toujours plus ciblées.&lt;/p&gt;
&lt;p&gt;Et que se passe-t-il lorsque ces tiers décident de clore un compte ou d'en
censurer un&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Ou encore si un service doit fermer (comme Google Reader dernièrement)&amp;nbsp;?
Combien de services, parfois très populaires, ont fermé durant ces dix
dernières années&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Et que deviennent vos données, une fois un compte supprimé&amp;nbsp;? Quelles sont les
garanties&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Avoir le contrôle sur ses données, c'est choisir de ne pas donner accès à cette
photographie désavantageuse prise lors d'une soirée trop alcoolisée et qui
pourrait nuire lors d'un entretien d'embauche.&lt;/p&gt;
&lt;p&gt;Ou encore profiter soi-même des revenus de la publicité sur la génération de
son contenu, ou tout simplement le mettre à disposition entièrement
gratuitement (comme le contenu de ce blog).&lt;/p&gt;
&lt;p&gt;Il est clair que d'avoir son adresse, son compte et son client mail tous au
même endroit, fournis par la même entité, c'est bien plus pratique, mais on est
alors entièrement dépendant de cette entité&amp;nbsp;: on connaît tous l'adage «&amp;nbsp;Il ne
faut pas mettre tous ses œufs dans le même panier&amp;nbsp;».&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="preserver-sa-vie-privee"&gt;
&lt;h3&gt;Préserver sa vie privée&lt;/h3&gt;
&lt;p&gt;Si vous pensez que vous n'avez de toute manière rien à cacher, posez-vous cette
simple question&amp;nbsp;: où est la limite&amp;nbsp;? Rien à cacher, ça veut dire qu'on peut
venir installer des caméras dans votre chambre à coucher&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Et fournir l'accès aux données à la
&lt;abbr title="National Security Agency, les services secrets des États-Unis"&gt;NSA&lt;/abbr&gt;, ou
automatiquement diminuer la zone «&amp;nbsp;privée&amp;nbsp;», en fournissant toujours plus de
vos informations aux autres&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Je vous recommande la visualisation de cette vidéo&amp;nbsp;: &lt;a class="reference external" href="http://korben.info/si-vous-navez-rien-a-cacher-alors-regardez-ceci.html"&gt;Si, vous avez quelque
chose à cacher&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-suite"&gt;
&lt;h2&gt;La suite&lt;/h2&gt;
&lt;p&gt;Le prochain article expliquera comment &lt;a class="reference external" href="//mathieu.agopian.info/blog/quitter-gmail-reserver-son-nom-de-domaine.html"&gt;Réserver son propre nom de domaine&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Au revoir Novapost, bonjour FruitLab</title><link href="//mathieu.agopian.info/blog/au-revoir-novapost-bonjour-fruitlab.html" rel="alternate"></link><published>2013-07-22T20:14:00+02:00</published><updated>2013-07-22T20:14:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-07-22:/blog/au-revoir-novapost-bonjour-fruitlab.html</id><summary type="html">&lt;p&gt;Novapost, c'est fini, ou plutôt, mon aventure au sein de Novapost s'est
terminée le vendredi 19 Juillet 2013.&lt;/p&gt;
&lt;p&gt;J'ai décidé, après moultes et moultes réflexions, questionnements et
tergiversations, de saisir une opportunité qui m'était offerte&amp;nbsp;: celle de
pouvoir me lancer à mon compte, en créant &lt;a class="reference external" href="http://fruitlab.org"&gt;FruitLab&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="novapost"&gt;
&lt;h2&gt;Novapost&lt;/h2&gt;
&lt;p&gt;C'est la larme …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Novapost, c'est fini, ou plutôt, mon aventure au sein de Novapost s'est
terminée le vendredi 19 Juillet 2013.&lt;/p&gt;
&lt;p&gt;J'ai décidé, après moultes et moultes réflexions, questionnements et
tergiversations, de saisir une opportunité qui m'était offerte&amp;nbsp;: celle de
pouvoir me lancer à mon compte, en créant &lt;a class="reference external" href="http://fruitlab.org"&gt;FruitLab&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="novapost"&gt;
&lt;h2&gt;Novapost&lt;/h2&gt;
&lt;p&gt;C'est la larme au coin de l'œil et le trémolo dans la voix que je vais vous
parler de mon passage chez Novapost.&lt;/p&gt;
&lt;p&gt;La meilleure boite de ma vie.&lt;/p&gt;
&lt;p&gt;J'en ai fait plusieurs, consultez mon &lt;a class="reference external" href="http://mathieu.agopian.info/Mathieu_AGOPIAN.pdf"&gt;CV&lt;/a&gt; si vous voulez un aperçu (toutes n'y
figurent pas), et je peux le dire sans peine et sans hésitation, Novapost a été
la meilleure expérience professionnelle que j'ai eu à vivre.&lt;/p&gt;
&lt;p&gt;J'en ai un peu parlé dans un précédent article sur la &lt;a class="reference external" href="//mathieu.agopian.info/blog/taxonomie-des-entreprises.html"&gt;taxonomie des
entreprises&lt;/a&gt;, Novapost correspond vraiment très bien à mes critères de choix.&lt;/p&gt;
&lt;div class="section" id="merci-a-l-equipe"&gt;
&lt;h3&gt;Merci à l'équipe&lt;/h3&gt;
&lt;p&gt;J'ai été recruté par Gregory et Lauréline, que j'avais eu la chance de
rencontrer auparavant par le biais des rencontres &lt;a class="reference external" href="http://rencontres.django-fr.org"&gt;DjangoFR&lt;/a&gt;. J'ai donc été le
troisième recruté d'une équipe qui compte maintenant 8 personnes.&lt;/p&gt;
&lt;p&gt;La quasi totalité des recrues étaient déjà connues du reste de l'équipe par le
biais de ces rencontres, et ça, c'est un confort inouï.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="merci-aux-collegues"&gt;
&lt;h3&gt;Merci aux collègues&lt;/h3&gt;
&lt;p&gt;Lors de mes passages dans les locaux, à Paris (je travaillais de chez moi, dans
la Drôme), j'ai aussi pu faire la connaissance de la trentaine d'autres
employés. Une joyeuse équipe de musiciens, jardiniers en herbe, bons vivants,
avec qui j'ai pu partager autour d'un repas dans un bon restaurant, ou troller
sur Java/Ruby autour d'une mousse.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="super-projet"&gt;
&lt;h3&gt;Super projet&lt;/h3&gt;
&lt;p&gt;«&amp;nbsp;Dématérialisation des feuilles de paie&amp;nbsp;». Pour faire simple, Novapost
développe un logiciel en mode &lt;abbr title="Software As A Service"&gt;SAAS&lt;/abbr&gt; à
destination des entreprises qui leur permet de gérer la totalité de
leurs documents de manière numérique&amp;nbsp;: éditer, envoyer, partager, stocker les
documents de manière électronique, là où il fallait auparavant imprimer,
envoyer puis stocker dans des armoires, aux archives...&lt;/p&gt;
&lt;p&gt;Une énorme liberté est laissée à l'équipe de développement sur les choix
technologiques et d'architecture, ce qui est très appréciable. On se sent
maître et responsable du projet, et non un bête exécutant, bon qu'à «&amp;nbsp;pisser du
code&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;De plus, les contributions open-source sont encouragées, il suffit de regarder
le &lt;a class="reference external" href="https://github.com/novagile/"&gt;compte github de Novapost&lt;/a&gt; pour s'en rendre compte. Chaque brique du
logiciel qui est assez découplée est ouverte, et il est courant de contribuer à
des librairies utilisées.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="merci-a-la-direction"&gt;
&lt;h3&gt;Merci à la direction&lt;/h3&gt;
&lt;p&gt;Orientée développeurs. Contrairement à la plupart des entreprises dans
lesquelles j'ai pu travailler auparavant, je me suis senti faire partie
intégrante de l'équipe, être un des maillons de la chaine, avoir de
l'importance.&lt;/p&gt;
&lt;p&gt;Trop souvent, d'après mon expérience, la direction ne voit les développeurs que
comme un poste de coût, qu'il faut limiter le plus possible.&lt;/p&gt;
&lt;p&gt;Se sentir considéré, apprécié, choyé même, ça change tout. Voici un aperçu des
avantages accordés à l'équipe&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;une demi-journée par semaine pour l'open-source&lt;/li&gt;
&lt;li&gt;deux conférences par an dont l'entrée est payée&lt;/li&gt;
&lt;li&gt;sponsoring de conférences&lt;/li&gt;
&lt;li&gt;télétravail&lt;/li&gt;
&lt;li&gt;bon fauteuil, bon bureau, bi-écran, matériel récent&lt;/li&gt;
&lt;li&gt;séminaire (deux jours dans un château de rêve, activités sportives, repas
gastronomiques...)&lt;/li&gt;
&lt;li&gt;repas de Noël (avec un cadeau personnalisé, choisi par les deux fondateurs&amp;nbsp;!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="mais-alors-pourquoi-partir"&gt;
&lt;h2&gt;Mais alors pourquoi partir&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;Désolé, aucune révélation sulfureuse ici&amp;nbsp;: j'ai simplement eu la possibilité de
me lancer à mon compte, chose qui me démangeait depuis plusieurs années.&lt;/p&gt;
&lt;p&gt;L'année dernière, j'ai participé à la &lt;a class="reference external" href="http://topcodingparty.net/"&gt;TopCodingParty&lt;/a&gt;, ou j'ai fait la
connaissance de Marc et Manu, les deux développeurs de &lt;a class="reference external" href="http://topchretien.com/"&gt;TopChrétien&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Quelques mois plus tard, j'ai reçu un mail de leur part me proposant une
mission de six mois pour refaire entièrement le site, afin de l'uniformiser et
d'apporter une cohérence a l'ensemble des fonctionnalités et sites annexes qui
ont été développés au fur et à mesure.&lt;/p&gt;
&lt;p&gt;Très rapidement, je me suis rendu compte que cette mission serait parfaite pour
moi&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;travailler pour un projet qui me tient à cœur&amp;nbsp;: répandre la bonne nouvelle&lt;/li&gt;
&lt;li&gt;possibilité de me lancer à mon compte, avec 6 mois de travail garanti&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="id1"&gt;
&lt;h2&gt;FruitLab&lt;/h2&gt;
&lt;p&gt;Le nom de mon EURL est parti d'une discussion avec Manu du TopChrétien&amp;nbsp;: le
projet était parti pour être développé en &lt;a class="reference external" href="http://symfony.com/"&gt;Symfony&lt;/a&gt;, et j'essayais de fournir
tous les arguments possibles pour utiliser un framework bien supérieur&amp;nbsp;:
&lt;a class="reference external" href="https://djangoproject.com"&gt;Django&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;C'est là qu'il m'a dit «&amp;nbsp;ce qui compte, c'est le fruit&amp;nbsp;», le résultat final, et
surtout ce que ça va apporter aux utilisateurs. C'est un thème récurrent dans
la Bible, et j'ai tellement aimé cette réponse que j'ai cherché un nom en
rapport.&lt;/p&gt;
&lt;p&gt;D'ailleurs merci à tous ceux qui m'ont fait des propositions de nom (oui, même
toi &lt;a class="reference external" href="https://twitter.com/m_clement"&gt;Martyn&lt;/a&gt; pour «&amp;nbsp;noix de cocode&amp;nbsp;», et toi &lt;a class="reference external" href="http://providenz.fr/"&gt;Laurent&lt;/a&gt; pour «&amp;nbsp;TropiCode&amp;nbsp;» ;).&lt;/p&gt;
&lt;p&gt;C'est ma sœur &lt;a class="reference external" href="http://claire.agopian.free.fr/"&gt;Claire&lt;/a&gt; qui a fini par trouver le nom final, à la fois facile à
prononcer et à épeler (beaucoup plus que la première idée qui était
«&amp;nbsp;CodeJuice&amp;nbsp;»).&lt;/p&gt;
&lt;div class="section" id="le-depart"&gt;
&lt;h3&gt;Le départ&lt;/h3&gt;
&lt;p&gt;J'ai eu la chance de pouvoir partir de Novapost en très bons termes. Clément,
un des deux fondateurs, m'a très bien reçu, a été très compréhensif, et même
encourageant. Il m'a permit de poser des congés durant ma période de préavis,
me permettant d'être présent pour le tout début du projet (ce qui a été décisif
dans le choix de Django ;).&lt;/p&gt;
&lt;p&gt;Voilà maintenant plusieurs semaines que je travaille sur le projet &lt;abbr title="TopChrétien Version Innovation"&gt;TopVI&lt;/abbr&gt;, sur lequel nous essayons de mettre en œuvre
toutes les bonnes pratiques&amp;nbsp;: tests unitaires et rapides, intégration continue,
revue de code, pair-programming, contributions open-source...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-suite"&gt;
&lt;h3&gt;La suite&lt;/h3&gt;
&lt;p&gt;Voilà une bonne question&amp;nbsp;: que se passera-t-il dans six mois, vers le début de
l'année 2014, à la fin de cette mission&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Quelques certitudes sur ce que je veux continuer à faire&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;participer le plus possibles aux conférences et évènements sur Python et
Django&lt;/li&gt;
&lt;li&gt;aider à l'organisation de DjangoCon Europe 2014&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/magopian/"&gt;contribuer&lt;/a&gt; à l'open-source, partager&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mais aussi trouver des projets, missions ou partenaires intéressants et qui me
permettront de m'épanouir, tout en apportant ma vision de la qualité, et me
concentrer sur le résultat final et les fruits qu'ils porteront.&lt;/p&gt;
&lt;p&gt;C'est le commencement d'une nouvelle aventure, dont je vous parlerai quand
l'arbre aura commencé à donner du fruit ;)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Retour sur Pytong 2013</title><link href="//mathieu.agopian.info/blog/retour-sur-pytong-2013.html" rel="alternate"></link><published>2013-06-24T10:22:00+02:00</published><updated>2013-06-24T10:22:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-06-24:/blog/retour-sur-pytong-2013.html</id><summary type="html">&lt;p&gt;Le 22 et 23 juin, à Toulon, s'est tenue la première édition de &lt;a class="reference external" href="http://pytong.org"&gt;Pytong&lt;/a&gt;,
organisée par &lt;a class="reference external" href="http://providenz.fr/"&gt;Laurent Paoletti&lt;/a&gt; et &lt;a class="reference external" href="https://larlet.fr/david/"&gt;David Larlet&lt;/a&gt; (merci à eux&amp;nbsp;!).&lt;/p&gt;
&lt;p&gt;Le format a reprit ce dont on a maintenant l'habitude pour les conférences
Django organisées par &lt;a class="reference external" href="http://django-fr.org"&gt;django-fr&lt;/a&gt;, une première journée de conférences et
barcamps, et une …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Le 22 et 23 juin, à Toulon, s'est tenue la première édition de &lt;a class="reference external" href="http://pytong.org"&gt;Pytong&lt;/a&gt;,
organisée par &lt;a class="reference external" href="http://providenz.fr/"&gt;Laurent Paoletti&lt;/a&gt; et &lt;a class="reference external" href="https://larlet.fr/david/"&gt;David Larlet&lt;/a&gt; (merci à eux&amp;nbsp;!).&lt;/p&gt;
&lt;p&gt;Le format a reprit ce dont on a maintenant l'habitude pour les conférences
Django organisées par &lt;a class="reference external" href="http://django-fr.org"&gt;django-fr&lt;/a&gt;, une première journée de conférences et
barcamps, et une deuxième journée de détente ensemble pour apprendre à mieux se
connaître.&lt;/p&gt;
&lt;div class="section" id="les-presentations"&gt;
&lt;h2&gt;Les présentations&lt;/h2&gt;
&lt;div class="section" id="reporting-web-et-print"&gt;
&lt;h3&gt;Reporting web et print&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://hashbang.fr"&gt;Arthur Vuillard&lt;/a&gt; nous a montré comment utiliser &lt;a class="reference external" href="http://pygal.org"&gt;pygal&lt;/a&gt; pour faire de
jolis reporting web avec de jolis graphes SVG annotés, et &lt;a class="reference external" href="http://weasyprint.org"&gt;weasyprint&lt;/a&gt; pour un
export en PDF propre, avec gestion des coupures de tableaux, centrage sur la
page, etc...&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://hashbang.fr/static/medias/pytong_reporting/index.html"&gt;Reporting web et print, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tu-peux-webtest"&gt;
&lt;h3&gt;Tu peux WebTest&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://twitter.com/gawel_"&gt;Gaël Pasgrimaud&lt;/a&gt;, mainteneur de l'excellente librairie &lt;a class="reference external" href="https://webtest.readthedocs.org/en/latest/"&gt;WebTest&lt;/a&gt; (créée par le
prolifique Ian Bicking), nous a expliqué comment l'utiliser pour tester nos
applications WSGI. Ça peut avantageusement remplacer le &lt;a class="reference external" href="https://docs.djangoproject.com/en/dev/topics/testing/overview/#module-django.test.client"&gt;Django Test Client&lt;/a&gt;,
étant donné ses fonctionnalités avancées, sa facilité d'utilisation, et le fait
que ça dialogue directement au niveau WSGI.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://gawel.github.io/pytong2013_webtest/#/tu-peux-webtest"&gt;Tu peux WebTest, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="des-migrations-sans-interruption-de-service"&gt;
&lt;h3&gt;Des migrations sans interruption de service&lt;/h3&gt;
&lt;p&gt;Thomas Chaumeny a listé plusieurs techniques et pratiques, ainsi que les
pièges à éviter pour faire des migrations de schéma de données (principalement
en utilisant &lt;a class="reference external" href="http://south.aeracode.org/"&gt;South&lt;/a&gt;), et ce sans interruption de service. En effet, selon la
migration, les changements dans la base de données peuvent bloquer une table
(verrou en écriture) pendant plusieurs minutes, voire heures. Il y a donc
certains pièges a éviter, et des méthodes à suivre.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://polyconseil.github.io/presentations/no_downtime_migrations/"&gt;Des migrations sans interruption de service, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="debuter-avec-salt"&gt;
&lt;h3&gt;Débuter avec salt&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://twitter.com/gwadeloop"&gt;Yann Malet&lt;/a&gt; nous a fait une rapide présentation de &lt;a class="reference external" href="http://saltstack.com"&gt;Salt&lt;/a&gt;, un gestionnaire de
configuration, un framework d'installation et d'exécution à distance. Pour ceux
qui connaissent &lt;a class="reference external" href="http://www.opscode.com/chef/"&gt;Chef&lt;/a&gt; ou &lt;a class="reference external" href="http://puppetlabs.com/"&gt;Puppet&lt;/a&gt;, Salt est un remplaçant écrit en Python, très
performant, car basé sur &lt;a class="reference external" href="http://zeromq.org"&gt;ZMQ&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il a ensuite expliqué comment, à &lt;a class="reference external" href="http://lincolnloop.com"&gt;LincolnLoop&lt;/a&gt;, ils ont utilisé Salt en tant que
framework d'exécution à distance pour leur nouveau projet de monitoring&amp;nbsp;:
&lt;a class="reference external" href="https://github.com/lincolnloop/salmon"&gt;Salmon&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deprime-au-bord-du-burn-out-et-pourtant-il-faut-continuer-a-coder"&gt;
&lt;h3&gt;Déprimé, au bord du burn-out, et pourtant il faut continuer à coder&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://j-mad.com"&gt;Jean-Michel Armand&lt;/a&gt; nous a fait part de son expérience de surmenage, de
comment il s'en est sorti, et comment il essaie de ne plus se mettre en danger
physiquement et psychologiquement. Un paquet de conseils avisés, à suivre avant
qu'il ne soit trop tard.&lt;/p&gt;
&lt;p&gt;On dit souvent qu'il faut vivre ses propres expériences pour pouvoir en
apprendre, et j'ai moi-même vécu une expérience similaire. Les conseils donnés
dans cette présentation sont précieux, et peuvent se résumer à «&amp;nbsp;vous valez
mieux que votre code/travail, vivez&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://speakerdeck.com/mrjmad/deprime-au-bord-du-burn-out-et-pourtant-il-faut-continuer-a-coder"&gt;Déprimé, au bord du burn-out... les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mezzanine-le-cms-des-developpeurs-pragmatiques"&gt;
&lt;h3&gt;Mezzanine, le CMS des développeurs pragmatiques&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://miximum.fr"&gt;Thibault Jouannic&lt;/a&gt; nous a parlé de &lt;a class="reference external" href="http://mezzanine.jupo.org/"&gt;Mezzanine&lt;/a&gt;, un CMS simple, performant et
facilement extensible, qui permet de répondre à des demandes simples sans avoir
à installer un Drupal ou Wordpress.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="use-zmq-and-tornado-for-fun-and-profit"&gt;
&lt;h3&gt;Use ZMQ and Tornado for fun and profit&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://feldboris.alwaysdata.net/blog/"&gt;Boris Feld&lt;/a&gt; nous a donné des recettes pour utiliser &lt;a class="reference external" href="http://zeromq.org"&gt;ZMQ&lt;/a&gt; avec &lt;a class="reference external" href="http://www.tornadoweb.org/"&gt;Tornado&lt;/a&gt; pour
faire le lien avec HTTP.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://speakerdeck.com/lothiraldan/use-omq-and-tornado-for-fun-and-profits"&gt;Use ZMQ and Tornado for fun and profit, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ecriture-d-un-livre-sur-django"&gt;
&lt;h3&gt;Écriture d'un livre sur Django&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://twitter.com/boblefrag"&gt;Yohann Gabory&lt;/a&gt; nous a parlé de son expérience d'écriture d'un livre sur
Django&amp;nbsp;: &lt;a class="reference external" href="http://www.eyrolles.com/Informatique/Livre/django-avance-9782212134155"&gt;Django avancé&lt;/a&gt;. Il en a profité pour nous expliquer comment écrire
une bonne documentation utilisateur, pour lui donner envie d'utiliser notre
projet/librairie/application...&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://fr.slideshare.net/YohannGabory/pytong-2013"&gt;Écriture d'un livre sur Django, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="python-dans-le-navigateur-et-pourquoi-pas"&gt;
&lt;h3&gt;Python dans le navigateur, et pourquoi pas&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://j-mad.com"&gt;Jean-Michel Armand&lt;/a&gt; nous a fait une démonstration de &lt;a class="reference external" href="http://brython.info"&gt;Brython&lt;/a&gt;, vu que toute
sa présentation était faite avec. Brython a pour ambition de remplacer
JavaScript dans le navigateur par le langage Python.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="python-script-par-python-pour-le-navigateur"&gt;
&lt;h3&gt;Python(Script) par Python pour le navigateur&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://plus.google.com/116302792447642827163/posts"&gt;Amirouche Boubekki&lt;/a&gt; nous a parlé de son projet &lt;a class="reference external" href="https://pythonscript.readthedocs.org/"&gt;PythonScript&lt;/a&gt;, une autre
alternative à Brython pour avoir du Python dans le navigateur.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="javascript-pour-les-developpeurs-python"&gt;
&lt;h3&gt;JavaScript pour les développeurs Python&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://twitter.com/n1k0"&gt;Nicolas Perriault&lt;/a&gt; a pris à revers les deux précédentes présentations
courtes&amp;nbsp;: il explique qu'il est inutile et futile de vouloir remplacer le
langage qui a été prévu exprès pour manipuler le DOM et être asynchrone (à base
de callbacks), exprès pour être exécuté dans les navigateurs.&lt;/p&gt;
&lt;p&gt;Le pragmatisme du développeur Python voudrait justement qu'il utilise les bons
outils pour les bonnes utilisations, et donc JavaScript pour du code dans le
navigateur.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="developper-une-app-django"&gt;
&lt;h3&gt;Développer une app Django&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://twitter.com/ouhouhsami"&gt;Samuel Goldszmidt&lt;/a&gt; s'est servi de l'exemple de son application
&lt;a class="reference external" href="https://github.com/ouhouhsami/django-select2light"&gt;Django-Select2Light&lt;/a&gt; pour montrer comment créer une application Django, en
utilisant &lt;a class="reference external" href="http://django-floppyforms.readthedocs.org/en/latest/"&gt;FloppyForms&lt;/a&gt; et &lt;a class="reference external" href="http://tastypieapi.org/"&gt;TastyPie&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://raw.github.com/ouhouhsami/pytong2013-LT-django-app-development-/master/slides.txt"&gt;Développer une app Django, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="utiliser-un-systeme-de-packaging-prive"&gt;
&lt;h3&gt;Utiliser un système de packaging privé&lt;/h3&gt;
&lt;p&gt;Brice Gelineau nous a expliqué comment il utilisait un système de packaging
privé pour son déploiement. C'est encore une autre alternative à l'utilisation
de &lt;a class="reference external" href="http://buildout.org"&gt;Buildout&lt;/a&gt; ou encore le &lt;a class="reference external" href="//mathieu.agopian.info/blog/le-miroir-pypi-du-pauvre.html"&gt;mirroir PyPI privé&lt;/a&gt; dont j'ai eu l'occasion de
parler lors d'une précédente conférence.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://polyconseil.github.io/presentations/private_packaging/"&gt;Utiliser un système de packaging privé, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-quoi-ressemblerait-mon-python"&gt;
&lt;h3&gt;À quoi ressemblerait mon python&amp;nbsp;?&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://twitter.com/duboisnicolas"&gt;Nicolas Dubois&lt;/a&gt; s'est demandé comment améliorer encore la lisibilité de nos
programmes Python. Il s'avère qu'avec quelques judicieuses modification, et
l'utilisation de caractères Unicode par exemple, nous pourrions avoir du code
source encore plus concis et expressif.&lt;/p&gt;
&lt;p&gt;Il y a peu de chances que nous ayons un interpréteur Python comprenant cette
syntaxe un jour, mais je trouve très intéressant de se poser ce genre de
questions, et nous avons commencé a écrire «&amp;nbsp;BMC&amp;nbsp;» (Beautify My Code) avec
Nicolas, petite librairie (service ?) qui permet d'opérer des
changements/remplacements sur un fichier source et d'afficher le résultat. À
suivre donc.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://git.nicolasdubois.com/talks/2013-pytong/"&gt;À quoi ressemblerait mon python, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="daybed-une-couche-de-validation-pour-couchdb"&gt;
&lt;h3&gt;Daybed, une couche de validation pour CouchDB&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="http://blog.antoine.cezar.fr/"&gt;Antoine Cezar&lt;/a&gt; nous a présenté le projet &lt;a class="reference external" href="http://daybed.readthedocs.org/en/latest/"&gt;Daybed&lt;/a&gt; dont il est un des
contributeurs. Cette surcouche à CouchDB, qui ajoute la validation de données,
permet d'avoir un remplaçant à &lt;a class="reference external" href="http://docs.google.com/forms"&gt;GoogleForms&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://github.com/AntoineCezar/pytong-2013-daybed-slides"&gt;Daybed, une couche de validation pour CouchDB, les slides&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-barcamps"&gt;
&lt;h2&gt;Les barcamps&lt;/h2&gt;
&lt;div class="section" id="les-web-components"&gt;
&lt;h3&gt;Les Web Components&lt;/h3&gt;
&lt;p&gt;Il y a eu un premier barcamp proposé par &lt;a class="reference external" href="https://larlet.fr/david/"&gt;David Larlet&lt;/a&gt; qui a fait l'unanimité
(oui, c'est bizarre d'avoir un seul et unique barcamp, ça s'oppose un peu à la
loi des deux pieds)&amp;nbsp;: une présentation des Web Components.&lt;/p&gt;
&lt;p&gt;Les Web Components ont à l'heure actuelle deux implémentations&amp;nbsp;: celle de
Mozilla avec &lt;a class="reference external" href="https://github.com/mozilla/xtags-org/tree/master/public"&gt;xtags&lt;/a&gt;, et celle de Google avec &lt;a class="reference external" href="http://www.polymer-project.org/"&gt;polymer&lt;/a&gt;. Ce sont des composants
qui peuvent être entièrement packagés et distribuables&amp;nbsp;: html, css et
JavaScript en un seul morceau.&lt;/p&gt;
&lt;p&gt;Ça me laisse une sorte d'impression de déjà vu, comment si on revenait aux
années sombres des «&amp;nbsp;clients lourds&amp;nbsp;» avec GUI, composants et widgets, etc...
je vois néanmoins l'intérêt que ces Web Components apportent alors qu'on
déporte de plus en plus de logique et de calcul sur le client, et qu'on cherche
à avoir des applications web de plus en plus proches, justement, des
applications natives.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comprendre-this-en-javascript"&gt;
&lt;h3&gt;Comprendre &amp;quot;this&amp;quot; en JavaScript&lt;/h3&gt;
&lt;p&gt;Suite à sa présentation courte sur «&amp;nbsp;JavaScript pour les développeurs Python&amp;nbsp;»,
&lt;a class="reference external" href="https://twitter.com/n1k0"&gt;Nicolas Perriault&lt;/a&gt; a indiqué les différentes utilisations et manières de
spécifier &lt;em&gt;this&lt;/em&gt; en JavaScript, ainsi que les &lt;a class="reference external" href="http://benalman.com/news/2010/11/immediately-invoked-function-expression/"&gt;IIFE&lt;/a&gt; et &lt;em&gt;use strict&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;J'avais déjà eu la chance de me pencher sur l'utilisation de &lt;em&gt;this&lt;/em&gt; grâce à un
lien que Nicolas m'avait fourni&amp;nbsp;: &lt;a class="reference external" href="http://ejohn.org/apps/learn/"&gt;Learning advanced JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="maitriser-git"&gt;
&lt;h3&gt;Maitriser git&lt;/h3&gt;
&lt;p&gt;Proposé par &lt;a class="reference external" href="http://miximum.fr"&gt;Thibault Jouannic&lt;/a&gt;, je n'ai pu y participer ayant assisté au
barcamp ci-dessus, mais j'en ai eu de bons retours.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="la-journee-detente"&gt;
&lt;h2&gt;La journée détente&lt;/h2&gt;
&lt;p&gt;Au programme&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;plage + baignade&amp;nbsp;: pour les plus courageux, l'eau n'étant pas très chaude, et
le vent était assez violent et frais&lt;/li&gt;
&lt;li&gt;slackline&amp;nbsp;: première fois pour moi, génial&amp;nbsp;! J'ai hâte de pouvoir en refaire&lt;/li&gt;
&lt;li&gt;repas&amp;nbsp;: bon, convivial, à l'ombre des mûriers platane, vue sur la mer, que
demander de plus&lt;/li&gt;
&lt;li&gt;jeux de société&amp;nbsp;: Dixit, Pandémie&lt;/li&gt;
&lt;li&gt;pétanque&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://marshmallowchallenge.com/Instructions.html"&gt;marshmallow challenge&lt;/a&gt; animé par &lt;a class="reference external" href="https://twitter.com/pointbar"&gt;Stéphane Langlois&lt;/a&gt;.Sympa de voir la
rétrospective, sur comment les enfants ont parfois de meilleurs résultats que
les jeunes ingénieurs ou commerciaux&amp;nbsp;!&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;C'est toujours un vrai plaisir de pouvoir rencontrer ses pairs, apprendre
d'eux, faire des connaissance, échanger des astuces et techniques. Je pense que
c'est un investissement indispensable à tout développeur passionné et curieux
qui souhaite évoluer et rester au courant des avancées dans son domaine.&lt;/p&gt;
&lt;p&gt;Vous pouvez par ailleurs consulter le &lt;a class="reference external" href="http://tech.novapost.fr/pytong-2013-a-toulon-le-resume.html"&gt;compte rendu de Rémy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Enfin, en petit bonus, je vous met le lien vers la présentation courte que
j'avais préparée «&amp;nbsp;au cas où&amp;nbsp;», mais que je n'ai pas eu l'occasion de montrer&amp;nbsp;:
&lt;a class="reference external" href="http://mathieu.agopian.info/presentations/2013_06_pytong/"&gt;Sécuriser ses données&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Retour sur Sud Web 2013</title><link href="//mathieu.agopian.info/blog/retour-sur-sud-web-2013.html" rel="alternate"></link><published>2013-05-24T12:06:00+02:00</published><updated>2013-05-24T12:06:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-05-24:/blog/retour-sur-sud-web-2013.html</id><summary type="html">&lt;p&gt;Le 17 et 18 mai, en Avignon, avait lieu la troisième édition de &lt;a class="reference external" href="http://sudweb.fr/2013/"&gt;Sud Web&lt;/a&gt;.
C'est une conférence que j'affectionne tout particulièrement, ayant eu la
chance de participer à son organisation à ses débuts.&lt;/p&gt;
&lt;p&gt;Le format est le suivant&amp;nbsp;: une première journée de présentations, courtes (5mn)
et longues (20mn + 5mn …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Le 17 et 18 mai, en Avignon, avait lieu la troisième édition de &lt;a class="reference external" href="http://sudweb.fr/2013/"&gt;Sud Web&lt;/a&gt;.
C'est une conférence que j'affectionne tout particulièrement, ayant eu la
chance de participer à son organisation à ses débuts.&lt;/p&gt;
&lt;p&gt;Le format est le suivant&amp;nbsp;: une première journée de présentations, courtes (5mn)
et longues (20mn + 5mn de questions), et une deuxième journée
d'«&amp;nbsp;élaboratoires&amp;nbsp;».&lt;/p&gt;
&lt;div class="section" id="les-presentations"&gt;
&lt;h2&gt;Les présentations&lt;/h2&gt;
&lt;p&gt;Les présentations étaient pour la plupart sous la forme de retours
d'expérience. C'est à mon humble avis le format le plus intéressant&amp;nbsp;: en effet,
les nouveautés techniques sont pléthores, et les technologies éprouvées sont
pour la plupart déjà connues.&lt;/p&gt;
&lt;p&gt;Surtout dans une conférence généraliste de la sorte, présenter un point
technique très précis et pointu serait une perte de temps pour la majorité de
l'audience.&lt;/p&gt;
&lt;div class="section" id="le-client-ce-gentil-mechant"&gt;
&lt;h3&gt;Le client, ce gentil méchant&lt;/h3&gt;
&lt;p&gt;Une petite piqûre de rappel&amp;nbsp;: pour le client, c'est nous les méchants
développeurs. Alors oui, souvent le client doit être éduqué, accompagné,
conseillé, et la relation est souvent ardue, mais il ne faut pas oublier que
dans l'autre sens c'est vrai aussi&amp;nbsp;: un client doit souvent éduquer et
conseiller un développeur, sur les côté produit/besoins.&lt;/p&gt;
&lt;p&gt;Il faut en finir avec le dialogue de sourd entre client et développeur, qui
nuit aux deux parties.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sass-et-compass-ont-embelli-mon-quotidien"&gt;
&lt;h3&gt;SASS et Compass ont embelli mon quotidien&lt;/h3&gt;
&lt;p&gt;Retour d'expérience sur l'utilisation de SASS (et de Compass) par rapport à un
seul fichier CSS de plusieurs dizaines de milliers de lignes, impossible à
maintenir.&lt;/p&gt;
&lt;p&gt;Medhi recommande d'ailleurs une architecture et un découpage des fichiers afin
de s'y retrouver plus rapidement, ainsi qu'une bonne documentation des règles
écrites&amp;nbsp;: justifier un décalage de 10pixels, expliciter l'utilité d'un mixin,
etc...&lt;/p&gt;
&lt;p&gt;Nous utilisons déjà SASS et Compass à Novapost, mais il reste un peu de travail
au niveau de l'organisation des fichiers et de leur découpage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-touchy-an-introduction-to-touch-events"&gt;
&lt;h3&gt;Getting touchy&amp;nbsp;: an introduction to touch events&lt;/h3&gt;
&lt;p&gt;Excellent exposé de Patrick sur les difficultés inhérentes à la gestions des
évènements &lt;em&gt;touch&lt;/em&gt; sur les différents &lt;em&gt;devices&lt;/em&gt; disponibles de nos jours.&lt;/p&gt;
&lt;p&gt;Plusieurs soucis à prendre en compte&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;la gestion des évènements n'est pas la même sur tous les navigateurs et tous
les &lt;em&gt;devices&lt;/em&gt; (ordre, nombre, type...)&lt;/li&gt;
&lt;li&gt;les &lt;em&gt;hacks&lt;/em&gt; peuvent être dangereux. Exemple&amp;nbsp;: il est possible de désactiver
le délai sur la détection d'un click (pour augmenter la réactivité et se
rapprocher d'une application native), mais attention au fait que ça désactive
par la même occasion la détection d'un double-click, le scroll, pinch &amp;amp;
zoom...&lt;/li&gt;
&lt;li&gt;il y a des &lt;em&gt;devices&lt;/em&gt; qui permettent d'utiliser une souris en parallèle du
&lt;em&gt;touch&lt;/em&gt;, comme certaines tablettes ou ordinateurs portables avec écran
tactile. Dans ce cas, il faut réagir aux évènements &lt;em&gt;touch&lt;/em&gt; mais aussi
&lt;em&gt;mouse&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="le-developpement-c-est-difficile"&gt;
&lt;h3&gt;Le développement c'est difficile&lt;/h3&gt;
&lt;p&gt;En prenant l'exemple de la gestion des dates et fuseaux horaire, Rémi nous
montre comment quelque chose qui a l'air simple en apparence, peut contenir des
pièges multiples dans lesquels même les plus grands (Apple, Microsoft...)
peuvent tomber.&lt;/p&gt;
&lt;p&gt;A Novapost, lors du passage en Django1.4, nous avons déjà fait en sorte
d'utiliser les fonctionnalités de gestion des fuseaux horaire développées par
Aymeric Augustin, et pour l'instant, avec succès. Ce n'est malheureusement pas
une solution définitive, à chaque fois qu'on joue avec des dates ou des heures,
il faut garder en tête la problématique et bien penser à ce qu'on fait pour ne
pas retomber dans le piège.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="aubergine-n-est-pas-une-couleur"&gt;
&lt;h3&gt;Aubergine n'est pas une couleur&lt;/h3&gt;
&lt;p&gt;Si le client vous parle d'une couleur, ne pas hésiter à lui faire confirmer
cette couleur par rapport à un référentiel couleur que vous auriez en commun.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="l-odyssee-de-l-espace-insecable"&gt;
&lt;h3&gt;L'odyssée de l'espace insécable&lt;/h3&gt;
&lt;p&gt;Eh non, il n'y a pas qu'un seul et unique espace insécable. Oui, le plus connu
est le &lt;tt class="docutils literal"&gt;&amp;amp;nbsp;&lt;/tt&gt;, mais il en existe plusieurs autres qui devraient être
utilisés en lieu et place de celui-ci. La meilleure solution étant
d'automatiser tout ça.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="et-si-j-ecrivais-un-livre"&gt;
&lt;h3&gt;Et si j'écrivais un livre&amp;nbsp;?&lt;/h3&gt;
&lt;p&gt;Retour de Corinne sur son aventure personnelle&amp;nbsp;: c'est une expérience qui a
l'air attirante, mais qui est parsemée d'embûches, et il faut surtout faire
attention à la gestion de son temps, de sa motivation, et des retours faits par
les relecteurs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="quete-mystere"&gt;
&lt;h3&gt;Quête mystère&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://larlet.fr/david/blog/2013/quete-sens/"&gt;La quête de sens&lt;/a&gt;, (non-)présentée et animée par David&amp;nbsp;: échange avec le
public et entre le public sur diverses questions et thèmes&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;argent&amp;nbsp;: qui ferait le même métier si il était bien moins bien payé
(exemple&amp;nbsp;: payé au smic). Une grande majorité de l'assistance a dit oui,
argumentant que beaucoup faisaient de l'open-source (bénévolement), et
faisaient leur métier par passion&lt;/li&gt;
&lt;li&gt;utilité&amp;nbsp;: la plupart sentent qu'ils ont beaucoup de pouvoir et d'utilité dans
leur domaine, mais pas forcément dans leur mission ou emploi actuel&lt;/li&gt;
&lt;li&gt;adrénaline&amp;nbsp;: peu de gens ont l'impression de faire ce métier pour
l'adrénaline de la mise en production, du débug en prod...&lt;/li&gt;
&lt;li&gt;santé&amp;nbsp;: constat alarmant, beaucoup ont des soucis de santé à cause de leur
travail. En effet, le propre d'une passion est de prendre toute la place, et
beaucoup se retrouvent en manque de sport, avec des soucis de dos, de stress,
de burnout&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="responsive-news-l-actualite-mobile-a-la-bbc"&gt;
&lt;h3&gt;Responsive news&amp;nbsp;: l'actualité mobile à la BBC&lt;/h3&gt;
&lt;p&gt;Kaelig nous a raconté son expérience à la BBC sur la mise en place de
«&amp;nbsp;responsive web design&amp;nbsp;» et leur méthode utilisée&amp;nbsp;: au lieu de gérer les
spécificités d'une multitude de &lt;em&gt;devices&lt;/em&gt;, ils ont décidé de placer un curseur,
et tous les téléphones qui supportent ce minimum de fonctionnalités ont la
version avancée (ajax, CSS3), les autres ayant une version statique basique.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-visualisation-de-donnees"&gt;
&lt;h3&gt;La visualisation de données&lt;/h3&gt;
&lt;p&gt;... comme outil pour découvrir et partager des idées sur le Web. Exposé fort en
images et animations de Nicolas, vraiment bluffant.&lt;/p&gt;
&lt;p&gt;On se rend compte que la bonne visualisation, selon le besoin, va avoir une
vraie valeur ajoutée&amp;nbsp;: découvrir les éléments qui sortent de la moyenne en
regardant les directions et forces du vent, mettre en relation/opposition des
tweets de candidats à l'élection présidentielle, ainsi que leur impact sur les
états...&lt;/p&gt;
&lt;p&gt;Certaines autres visualisations sont purement artistiques.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="les-super-pouvoirs-du-nouveau-venu"&gt;
&lt;h3&gt;Les super-pouvoirs du nouveau venu&lt;/h3&gt;
&lt;p&gt;Par votre humble serviteur (je n'ai pas pu assister aux deux présentations
courtes précédente, vu que je me préparais en coulisse).&lt;/p&gt;
&lt;p&gt;En attendant la vidéo, vous pouvez &lt;a class="reference external" href="http://agopian.info/presentations/2013_05_sudweb/"&gt;trouver le support ici&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le but était de montrer qu'en mettant en place un système de mentorat, une
mise en relation entre un ancien et un nouveau, on a tout à y gagner.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="travailler-sur-ses-deux-pieds"&gt;
&lt;h3&gt;Travailler sur ses deux pieds&lt;/h3&gt;
&lt;p&gt;Considérations sur l'impact négatif sur la santé de travailler en position
assise toute la journée. De plus en plus de développeurs optent pour travailler
au moins une partie de la journée debout devant leur bureau (surélevé par
divers moyens).&lt;/p&gt;
&lt;p&gt;Je teste cette méthode depuis plusieurs mois avec bonheur (et un soulagement au
niveau du dos). Pour ne pas trop stresser genoux et chevilles, j'alterne
régulièrement, passant parfois des jours d'affilée assis, et d'autre debout, ou
alternant plusieurs fois dans la même journée.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="monitoring-une-culture-plus-que-des-outils"&gt;
&lt;h3&gt;Monitoring&amp;nbsp;: une culture plus que des outils&lt;/h3&gt;
&lt;p&gt;Différentes expériences malheureuses ont conduit le journal &lt;a class="reference external" href="http://20minutes.fr"&gt;20minutes.fr&lt;/a&gt; a
mettre en place un monitoring très fin des différentes parties de leur système.
Ça leur a permis de détecter très tôt (parfois avant même le retour de leurs
utilisateurs) de problèmes lors de la mise en production par exemple.&lt;/p&gt;
&lt;p&gt;À Novapost nous avons depuis peu trois gyrophares en plus du monitoring par
Sentry et New Relic&amp;nbsp;: ils nous permettent de voir de manière ludique quelques
indicateurs de notre plateforme.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="comment-l-impression-3d-va-modifier-le-web-et-l-economie"&gt;
&lt;h3&gt;Comment l'impression 3D va modifier le Web et l'économie&lt;/h3&gt;
&lt;p&gt;Marc, avocat très versé dans les nouvelles technologies, nous montre comment le
futur, c'est maintenant. La répliqueuse de Star Trek, qui nous faisait rêver en
étant enfant, devient une réalité avec les imprimantes 3D qui permettent
d'imprimer des organes humains, des bonbons, des objets de tout type.&lt;/p&gt;
&lt;p&gt;Cela aura obligatoirement un fort impact sur les utilisations, l'économie et la
perception des droits d'auteur.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="les-elaboratoires"&gt;
&lt;h2&gt;Les élaboratoires&lt;/h2&gt;
&lt;p&gt;Les élaboratoires sont aussi souvent appelés «&amp;nbsp;barcamps&amp;nbsp;». Le principe est
simple&amp;nbsp;: plusieurs scéances dans la journée, proposées pour certaines avant le
jour J, d'autres le jour même, sur différents sujets, problématiques. Le but
unique est l'échange entre les participants, et est régi par la règle des deux
pieds&amp;nbsp;: si tu n'as rien à apprendre ni rien à apporter, change de salle.&lt;/p&gt;
&lt;p&gt;J'y ai appris comme faire des &lt;em&gt;mockups&lt;/em&gt; d'applications mobiles, et pourquoi
Didier s'est noyé dans son code (enquête en fil rouge tout au long de la
journée sur la mise en place de méthodes agiles, l'importance de se soutenir
dans une équipe...).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Encore une excellente édition&amp;nbsp;: le plus important, non décrit ici, c'est la
possibilité d'échanges, de découvertes et de rencontres durant les pauses, lors
des soirées communautaires, tout au long de la journée d'élaboratoires.&lt;/p&gt;
&lt;p&gt;Ces échanges et rencontres, ainsi que les présentations, m'ont permis de
repartir plein d'énergie, d'idées, d'envies d'expérimentations.&lt;/p&gt;
&lt;p&gt;Ce genre de rencontre est à mon sens indispensable à tout développeur pour lui
éviter de s'enfermer dans sa cave, de se sur-spécialiser, de perdre le contact
avec ses pairs, et par là-même, de devenir obsolète.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Django1.5 : passer au Configurable User Model</title><link href="//mathieu.agopian.info/blog/django15-passer-au-configurable-user-model.html" rel="alternate"></link><published>2013-04-16T14:13:00+02:00</published><updated>2013-04-16T14:13:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-04-16:/blog/django15-passer-au-configurable-user-model.html</id><summary type="html">&lt;p&gt;Depuis la version 1.5 de Django, il est possible d'utiliser un &lt;a class="reference external" href="https://docs.djangoproject.com/en/1.5/topics/auth/customizing/#auth-custom-user"&gt;Configurable
User Model&lt;/a&gt; en lieu et place de &lt;tt class="docutils literal"&gt;django.contrib.auth.User&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Cela permet, par exemple, de se passer de &lt;em&gt;proxy model&lt;/em&gt; ou encore de fusionner
le profil avec l'utilisateur, pour éviter des &lt;em&gt;join&lt;/em&gt; dans les requêtes SQL …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Depuis la version 1.5 de Django, il est possible d'utiliser un &lt;a class="reference external" href="https://docs.djangoproject.com/en/1.5/topics/auth/customizing/#auth-custom-user"&gt;Configurable
User Model&lt;/a&gt; en lieu et place de &lt;tt class="docutils literal"&gt;django.contrib.auth.User&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Cela permet, par exemple, de se passer de &lt;em&gt;proxy model&lt;/em&gt; ou encore de fusionner
le profil avec l'utilisateur, pour éviter des &lt;em&gt;join&lt;/em&gt; dans les requêtes SQL.&lt;/p&gt;
&lt;p&gt;Très pratique, et facile à mettre en place sur un projet qui commence juste,
mais comment gérer ça en utilisant &lt;a class="reference external" href="http://south.aeracode.org/"&gt;South&lt;/a&gt; sur un projet déjà bien en place&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Le but est donc de fusionner l'utilisateur et le profil, avec pour
aide/contrainte d'utiliser South, autant sur des plateformes existantes
(serveur de production, de pré-production) que sur les plateformes de
développement&amp;nbsp;: donc les migrations doivent fonctionner sur une création de
base, tout autant que sur une migration simple.&lt;/p&gt;
&lt;p&gt;Nous allons détailler plusieurs stratégies.&lt;/p&gt;
&lt;div class="section" id="contexte"&gt;
&lt;h2&gt;Contexte&lt;/h2&gt;
&lt;p&gt;Notre projet utilise depuis longtemps un &lt;em&gt;proxy model&lt;/em&gt; sur l'utilisateur, ne
rajoutant que quelques méthodes. Toutes les données liées à l'utilisateur sont
par ailleurs stockées dans un profil, qui est utilisé par le biais de
&lt;tt class="docutils literal"&gt;get_profile()&lt;/tt&gt; (et le &lt;em&gt;setting&lt;/em&gt; &lt;tt class="docutils literal"&gt;AUTH_PROFILE_MODULE&lt;/tt&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RH2User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="o"&gt;...&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RH2UserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;some_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;some_other_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Le résultat, une fois le profil fusionné avec l'utilisateur&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AbstractUser&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RH2User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;some_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;some_other_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ne pas oublier de fusionner les &lt;em&gt;managers&lt;/em&gt;, les méthodes &lt;tt class="docutils literal"&gt;save()&lt;/tt&gt;, et de
dédoublonner les champs ayant le même nom (dans notre cas,
&lt;tt class="docutils literal"&gt;RH2UserProfile.last_login&lt;/tt&gt; a été renommé en &lt;tt class="docutils literal"&gt;RH2User.previous_last_login&lt;/tt&gt;,
étant donné que le modèle &lt;tt class="docutils literal"&gt;auth.User&lt;/tt&gt; d'origine avait déjà un champ
&lt;tt class="docutils literal"&gt;last_login&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;Il faut par ailleurs rechercher et remplacer le cas échéant toutes les
occurrences de&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;RH2UserProfile&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;get_profile()&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;rh2userprofile__&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;&lt;tt class="docutils literal"&gt;.user&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="la-problematique"&gt;
&lt;h2&gt;La problématique&lt;/h2&gt;
&lt;p&gt;À partir du moment où le paramètre &lt;tt class="docutils literal"&gt;AUTH_USER_MODEL&lt;/tt&gt; est renseigné&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;les tables &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;auth_user_user_permissions&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;auth_user_groups&lt;/tt&gt; ne sont plus automatiquement crées par un &lt;tt class="docutils literal"&gt;python
manage.py syncdb&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;toutes les migrations South existantes sur des modèles ayant une &lt;em&gt;ForeignKey&lt;/em&gt;
ou &lt;em&gt;Many to Many&lt;/em&gt; ne passerons plus tel quel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il y a donc principalement deux stratégies pour les migrations South, une fois
qu'on a notre modèle &lt;tt class="docutils literal"&gt;RH2User&lt;/tt&gt; complet (et non plus &lt;em&gt;proxy&lt;/em&gt;) ainsi que
&lt;tt class="docutils literal"&gt;AUTH_USER_MODEL = 'account.RH2User'&lt;/tt&gt; dans les paramètres&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Modifier la migration initiale de l'app &lt;em&gt;account&lt;/em&gt;, puis toutes les
migrations suivantes ainsi que les migrations des app ayant une relation
avec l'utilisateur pour qu'elles se basent sur &lt;tt class="docutils literal"&gt;account_rh2user&lt;/tt&gt; au lieu
de &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;Rajouter la création de la table &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;auth_user_user_permissions&lt;/tt&gt; et &lt;tt class="docutils literal"&gt;auth_user_groups&lt;/tt&gt; dans la migration
initiale de l'app contenant le modèle complet, puis rajouter une migration
qui va renommer la table &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt; en &lt;tt class="docutils literal"&gt;account_rh2user&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans les deux cas, il faudra être attentif à l'ordre d'exécution des
migrations&amp;nbsp;: toutes les applications ayant une relation avec l'utilisateur
devront dépendre de la migration initiale qui crée la table &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt; ou
&lt;tt class="docutils literal"&gt;account_rh2user&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Dans le deuxième cas, il faudra de plus que la première des migrations suivant
le renommage, pour chaque application, dépende de cette migration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creation-de-account-rh2user-et-modification-des-migrations"&gt;
&lt;h2&gt;Création de account_rh2user et modification des migrations&lt;/h2&gt;
&lt;p&gt;Le plus simple est de créer une migration de schéma pour avoir le code
nécessaire à la migration &lt;tt class="docutils literal"&gt;0001_initial&lt;/tt&gt; de l'application account&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ python manage.py schemamigration account
&lt;/pre&gt;
&lt;p&gt;Il suffit alors de recopier le code de la migration créée, de le rajouter au
fichier &lt;tt class="docutils literal"&gt;account/migrations/0001_initial.py&lt;/tt&gt;, puis de supprimer cette
nouvelle migration qui ne sera pas utilisée.&lt;/p&gt;
&lt;p&gt;Il faut ensuite modifier chacune des migrations, en prenant exemple sur ce qui
a été fait sur &lt;a class="reference external" href="https://github.com/caffeinehit/django-oauth2-provider/pull/18/files"&gt;django-oauth2-provider&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il reste la problématique de la migration des serveurs déjà en production (qui
ont déjà un certain nombre de migrations effectuées, et une base de donnée à
conserver). Une solution serait de créer une migration de données et de tester
l'existence de la table &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt;, et le cas échéant de dupliquer les
données dans la table &lt;tt class="docutils literal"&gt;account_rh2user&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;N'ayant pas testé cette solution, je ne peux la garantir.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creation-de-auth-user-puis-renommage"&gt;
&lt;h2&gt;Création de auth_user puis renommage&lt;/h2&gt;
&lt;p&gt;C'est la solution que nous avons choisi, étant donné le nombre de migrations
que nous avons (près d'une centaine), qu'il aurait fallu modifier une à une,
ainsi que le soucis de migration des serveurs déjà en production.&lt;/p&gt;
&lt;p&gt;Il faut dans l'ordre&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;créer les tables &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;auth_user_user_permissions&lt;/tt&gt; et
&lt;tt class="docutils literal"&gt;auth_user_groups&lt;/tt&gt; dans la migration &lt;tt class="docutils literal"&gt;0001_initial&lt;/tt&gt; de account&lt;/li&gt;
&lt;li&gt;créer une migration dans account qui renomme la table &lt;tt class="docutils literal"&gt;auth_user&lt;/tt&gt; en
&lt;tt class="docutils literal"&gt;account_rh2user&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;créer une migration dans account qui rajoute les champs du modèle profil à
l'utilisateur&lt;/li&gt;
&lt;li&gt;créer une migration de données pour dupliquer toutes les données de profil
dans la table &lt;tt class="docutils literal"&gt;account_rh2user&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;pour chaque application ayant une relation vers l'utilisateur, la prochaine
migration créée devra dépendre de la migration qui renomme la table&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Le plus compliqué dans toute cette histoire est la gestion de dépendances entre
les migrations.&lt;/p&gt;
&lt;p&gt;Une autre solution non évoquée aurait été de repartir de 0 pour les
migrations&amp;nbsp;: supprimer toutes les migrations existantes, ainsi que la table
&lt;tt class="docutils literal"&gt;south_migrationhistory&lt;/tt&gt;, puis reconvertir toutes les applications à South&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ python manage.py convert_to_south ....
&lt;/pre&gt;
&lt;p&gt;L'avantage est qu'il n'y a alors aucun soucis de dépendances entre les
migrations, et qu'on repart de quelque chose de propre.&lt;/p&gt;
&lt;p&gt;Les inconvénients sont multiples&amp;nbsp;: gérer une migration (à la main?) pour les
plateformes en cours d'utilisation, impossibilité de retourner en arrière
automatiquement, perte de l'historique...&lt;/p&gt;
&lt;p&gt;Il y a une autre possibilité (à tester&amp;nbsp;!) qui consiste à spécifier l'attribut
&lt;tt class="docutils literal"&gt;db_table = 'auth_user'&lt;/tt&gt; dans la &lt;em&gt;Meta&lt;/em&gt; de notre nouveau modèle &lt;tt class="docutils literal"&gt;RH2User&lt;/tt&gt;,
pour qu'il utilise exactement la même table. En théorie, il n'y a alors pas
besoin de migration, mais il reste à gérer la fusion du profil dans
l'utilisateur.&lt;/p&gt;
&lt;/div&gt;
</content><category term="django"></category></entry><entry><title>Le sport</title><link href="//mathieu.agopian.info/blog/le-sport.html" rel="alternate"></link><published>2013-02-26T08:27:00+01:00</published><updated>2013-02-26T08:27:00+01:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-02-26:/blog/le-sport.html</id><summary type="html">&lt;div class="section" id="ma-vie"&gt;
&lt;h2&gt;Ma vie&lt;/h2&gt;
&lt;p&gt;J'ai été sportif, voire très sportif, dans ma jeunesse. Étant étudiant, je
pratiquais le rugby, le badminton et l'aïkido de manière intensive. Je suis
passé par des périodes où je faisais 10h d'entraînement par semaine, tellement
ça me plaisait.&lt;/p&gt;
&lt;p&gt;Puis un jour j'ai trouvé un travail, et je …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="ma-vie"&gt;
&lt;h2&gt;Ma vie&lt;/h2&gt;
&lt;p&gt;J'ai été sportif, voire très sportif, dans ma jeunesse. Étant étudiant, je
pratiquais le rugby, le badminton et l'aïkido de manière intensive. Je suis
passé par des périodes où je faisais 10h d'entraînement par semaine, tellement
ça me plaisait.&lt;/p&gt;
&lt;p&gt;Puis un jour j'ai trouvé un travail, et je m'y suis investi à corps perdu (que
cette expression est juste, quand on y pense...).&lt;/p&gt;
&lt;p&gt;J'étais jeune, motivé, je voulais faire mes preuves, et je ne comptais pas mes
heures ni l'énergie que j'y dépensais. Du jour au lendemain, j'ai arrêté toute
forme de sport, n'ayant plus de temps à y consacrer.&lt;/p&gt;
&lt;p&gt;Par contre, j'ai continué à manger autant. Résultat, +20kg en six mois. Je
mesure 1m85, et mon poids de forme doit être aux alentours de 90kg. Je suis
monté très rapidement à 120kg.&lt;/p&gt;
&lt;p&gt;Par la suite, j'ai essayé de me remettre au sport, épisodiquement, pour
reperdre un peu, diminuer le grignotage, mais tôt ou tard je retombais dans les
mauvaises habitudes&amp;nbsp;: pas de sport (ou peu, une séance de 2h de badminton par
semaine), grignotage.&lt;/p&gt;
&lt;p&gt;Et au travail, je ressentais du stress, beaucoup, jusqu'à arriver aux portes du
&lt;a class="reference external" href="https://fr.wikipedia.org/wiki/Syndrome_d%27%C3%A9puisement_professionnel"&gt;burnout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En parallèle, j'ai commencé à me détériorer physiquement&amp;nbsp;: j'ai maintenant 34
ans, un tassement des lombaires, des douleurs chroniques dans le dos, la nuque,
le genou droit (ménisque intérieur détruit), les chevilles.&lt;/p&gt;
&lt;p&gt;Je suis passé par une période de dépression alors que j'ai tout pour faire mon
bonheur&amp;nbsp;: une femme merveilleuse qui m'aime, une fille adorable et en bonne
santé, un travail intéressant, motivant, bien payé, au sein d'une équipe au
top, et d'une équipe de direction axée vers les développeurs (fait assez rare
pour être souligné).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-poule-ou-l-oeuf"&gt;
&lt;h2&gt;La poule ou l'œuf&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;Est-ce le stress au travail qui a engendré mon surpoids et ma mauvaise
condition physique&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Ou est-ce au contraire mon surpoids et ma mauvaise condition physique qui ont
engendré du stress&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Je pense que les deux sont liés, d'une certaine manière, et j'ai décidé de
mener l'expérience en agissant sur l'un des deux facteurs&amp;nbsp;: la condition
physique.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="le-sport-le-remede-a-tous-les-problemes"&gt;
&lt;h2&gt;Le sport, le remède à tous les problèmes&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;Je ne sais pas si c'est la panacée, mais je peux parler de mon expérience.&lt;/p&gt;
&lt;p&gt;Un peu avant décembre 2012, je me suis acheté un vélo elliptique, et je me suis
décidé à en faire 40 minutes tous les matins au lever, 5 jours par semaine
(repos le week-end).&lt;/p&gt;
&lt;p&gt;J'en suis au 44ème jour de pratique, et hormis cas de force majeure (cloué au
lit par la grippe, en déplacement), je m'y suis tenu sans faute.&lt;/p&gt;
&lt;p&gt;Ce que ça m'apporte&amp;nbsp;: j'ai perdu environ 5kg, beaucoup de gras, beaucoup de
stress, je me sens détendu la plupart du temps, bien dans mes baskets, mes
douleurs chroniques sont moins courantes... d'une manière générale, je me sens
en meilleure forme, posé, recentré, capable, plus léger dans mon corps et dans
ma tête.
Je ne ressens que très rarement le besoin de grignoter (qui était lié à mon
stress), je mange moins, plus conscient du mal que je fais à mon corps.&lt;/p&gt;
&lt;p&gt;Ce que ça me coûte&amp;nbsp;: me lever 1h plus tôt chaque matin (zut, moins de temps
pour m'abrutir devant la télé le soir).&lt;/p&gt;
&lt;p&gt;Je suis loin d'avoir regagné mon poids de forme, mais je suis loin d'avoir
envie d'arrêter de pratiquer du sport régulièrement, donc tout va bien&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="toi-aussi-tu-le-peux"&gt;
&lt;h2&gt;Toi aussi, tu le peux&lt;/h2&gt;
&lt;p&gt;Longtemps, je me suis trouvé des excuses pour ne pas m'y mettre&amp;nbsp;: pas le temps,
pas l'envie, pas la motivation, trop ennuyeux (petite astuce&amp;nbsp;: je me suis
procuré une liseuse électronique, et chaque matin je peux maintenant bouquiner
pendant 40mn ;).&lt;/p&gt;
&lt;p&gt;Quand je vois le gain par rapport à ce que ça me coûte, je regrette de ne pas
m'y être mis plus tôt, d'avoir laissé se dégrader ma santé et mon mental pour
de faux prétextes.&lt;/p&gt;
&lt;p&gt;On entends souvent dire «&amp;nbsp;le travail, c'est la santé&amp;nbsp;», mais on ne peut pas
bien travailler sans la santé. La santé, on se la doit, à nous, à notre
entourage, à notre employeur, ça ne devrait pas être une option mais un
prérequis.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Création d'un FabLab sur Valence</title><link href="//mathieu.agopian.info/blog/creation-dun-fablab-sur-valence.html" rel="alternate"></link><published>2013-02-07T09:00:00+01:00</published><updated>2013-02-07T09:00:00+01:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-02-07:/blog/creation-dun-fablab-sur-valence.html</id><summary type="html">&lt;p&gt;Petit message d'annonce&amp;nbsp;: un &lt;a class="reference external" href="https://fr.wikipedia.org/wiki/Fablab"&gt;FabLab&lt;/a&gt;
s'est créé à Valence, et nous avons eu notre première rencontre ce mardi 5
février, dans les locaux de l'&lt;a class="reference external" href="http://www.iut-valence.fr/"&gt;IUT&lt;/a&gt;, et plus
précisément dans le bâtiment E.&lt;/p&gt;
&lt;p&gt;Cette première rencontre concrétise cinq précédentes réunions de motivés&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;il y a plusieurs mois, j'ai proposé à …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Petit message d'annonce&amp;nbsp;: un &lt;a class="reference external" href="https://fr.wikipedia.org/wiki/Fablab"&gt;FabLab&lt;/a&gt;
s'est créé à Valence, et nous avons eu notre première rencontre ce mardi 5
février, dans les locaux de l'&lt;a class="reference external" href="http://www.iut-valence.fr/"&gt;IUT&lt;/a&gt;, et plus
précisément dans le bâtiment E.&lt;/p&gt;
&lt;p&gt;Cette première rencontre concrétise cinq précédentes réunions de motivés&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;il y a plusieurs mois, j'ai proposé à &lt;a class="reference external" href="http://www.logilab.org/cwuser/nchauvat"&gt;Nicolas Chauvat&lt;/a&gt; de se faire un &lt;a class="reference external" href="http://afpy.ro/"&gt;afpyro&lt;/a&gt;, vu que nous étions tous les deux pythonistes et dans la
région. Nous y avons rencontré &lt;a class="reference external" href="http://karekinian.com/"&gt;Gregory Karékinian&lt;/a&gt;, qui nous a mis en relation avec le &lt;a class="reference external" href="https://twitter.com/CARAValence"&gt;CARA Valence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Nicolas a ensuite assisté à une présentation Open Data au &lt;a class="reference external" href="http://www.pole-numerique.fr/accueil.html"&gt;Pôle Numérique&lt;/a&gt; où il a rencontré Alexis, et
lui a parlé de l'idée du FabLab&lt;/li&gt;
&lt;li&gt;le mardi 8 janvier, nous nous sommes retrouvés, avec Nicolas, au CARA, et
Nicolas a lancé l'idée d'un FabLab sur Valence, ce qui a immédiatement
enthousiasmé Alexis, Collin et Pascal&lt;/li&gt;
&lt;li&gt;quelques semaines après, nous nous retrouvions à nouveau autour d'une bière en
compagnie de &lt;a class="reference external" href="http://sebastienjean.fr/"&gt;Sébastien&lt;/a&gt;, enseignant
à l'IUT de Valence&lt;/li&gt;
&lt;li&gt;puis ce fût une rencontre au Pôle Numérique avec une bonne douzaine de gens
décidés et motivés à se retrouver deux fois par mois&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et voilà comment naquit ce FabLab, partant d'une petite rencontre anodine
autour d'une bière. Ce mouvement nous a totalement dépassé pour maintenant
appartenir à tout un groupe de passionnés.&lt;/p&gt;
&lt;p&gt;Je tiens à remercier chaleureusement Sébastien qui a été un réel moteur pour
cette première réunion, qui a engagé toutes les démarches nécessaires auprès de
sa direction, ainsi que l'IUT qui nous met à disposition ses salles et son
matériel (oscilloscopes, matériel électronique de base, salles de classe...).&lt;/p&gt;
&lt;p&gt;Nous étions donc une bonne vingtaine, groupe hétéroclite de gens qui découvrent
tout juste l'électronique ou la CAO, des étudiants et des professionnels de
différents horizons et spécialités.&lt;/p&gt;
&lt;p&gt;Je vous donne donc rendez-vous pour les prochaines rencontres qui ont lieu tous
les premiers mardi ainsi que tous les troisièmes mercredi de chaque mois, la
prochaine étant le 20 février (je ne pourrai malheureusement pas y assister).&lt;/p&gt;
&lt;p&gt;Pour vous inscrire et suivre les différents événements et rencontres&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;se créer un compte sur &lt;a class="reference external" href="https://veille.pole-numerique.com"&gt;https://veille.pole-numerique.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;rejoindre le groupe &lt;em&gt;fablab&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;rejoindre la liste de diffusion &lt;a class="reference external" href="mailto:fablab.valence&amp;#64;librelist.com"&gt;fablab.valence&amp;#64;librelist.com&lt;/a&gt; (il suffit d'y
envoyer un premier mail qui ne sera pas envoyé à la liste pour s'y inscrire)&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"></category></entry><entry><title>Lancement de FaitMain.org</title><link href="//mathieu.agopian.info/blog/lancement-de-faitmainorg.html" rel="alternate"></link><published>2013-02-02T08:56:00+01:00</published><updated>2013-02-02T08:56:00+01:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2013-02-02:/blog/lancement-de-faitmainorg.html</id><summary type="html">&lt;p&gt;Depuis hier, le tout nouveau magazine &lt;a class="reference external" href="http://faitmain.org"&gt;faitmain.org&lt;/a&gt; est
officiellement lancé&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;J'ai une affection toute particulière pour ce magazine, non seulement parce que
j'y ai (modestement) contribué, mais surtout parce que c'est un domaine (ou
plutôt croisement de domaines) qui me parle particulièrement depuis plusieurs
années&amp;nbsp;: l'application de l'informatique et …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Depuis hier, le tout nouveau magazine &lt;a class="reference external" href="http://faitmain.org"&gt;faitmain.org&lt;/a&gt; est
officiellement lancé&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;J'ai une affection toute particulière pour ce magazine, non seulement parce que
j'y ai (modestement) contribué, mais surtout parce que c'est un domaine (ou
plutôt croisement de domaines) qui me parle particulièrement depuis plusieurs
années&amp;nbsp;: l'application de l'informatique et de la programmation à notre vie de
tous les jours.&lt;/p&gt;
&lt;p&gt;Comme on peut le lire dans &lt;a class="reference external" href="http://www.faitmain.org/volume-1/edito.html"&gt;l'édito&lt;/a&gt;, le magazine couvre plusieurs
domaines ou sujets&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;l'informatique&lt;/li&gt;
&lt;li&gt;l'électronique&lt;/li&gt;
&lt;li&gt;la cuisine&lt;/li&gt;
&lt;li&gt;l'art&lt;/li&gt;
&lt;li&gt;l'écologie&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce qui ne gâche rien, bien au contraire, c'est que tout est open-source et
&lt;a class="reference external" href="https://github.com/faitmain"&gt;disponible sur github&lt;/a&gt;, que ce soit les
articles, le raccourcisseur d'url, le moteur de recherche et le moteur même du
magazine. N'hésitez pas à y faire un tour, et y contribuer par la création de
tickets si vous trouvez un bug, une typo, ou que vous avez une fonctionnalité à
proposer&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;Ce magazine rejoint quelques unes de mes interrogations et actions de ces
derniers mois&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;mon goût de plus en plus prononcé pour la domotique&lt;/li&gt;
&lt;li&gt;ma passion toujours présente pour la robotique&lt;/li&gt;
&lt;li&gt;le projet de création de FabLab sur Valence&lt;/li&gt;
&lt;li&gt;ma &lt;a class="reference external" href="http://www.raspberrypi.org/"&gt;Raspberry Pi&lt;/a&gt; qui trône sur mon bureau et
qui me nargue depuis de nombreux mois&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, beaucoup d'idées de projets, de motivation et d'enthousiasme. Je vous
recommande très chaleureusement la lecture de ce petit bijou réalisé avec
amour, et pourquoi pas, pour le second numéro à venir, de proposer un article&amp;nbsp;?&lt;/p&gt;
</content><category term="misc"></category></entry><entry><title>Djangocon 2012 Tolosa Edition</title><link href="//mathieu.agopian.info/blog/djangocon-2012-tolosa-edition.html" rel="alternate"></link><published>2012-11-29T08:37:00+01:00</published><updated>2012-11-29T08:37:00+01:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2012-11-29:/blog/djangocon-2012-tolosa-edition.html</id><summary type="html">&lt;p&gt;Ce 24 et 25 novembre se tenait la rencontre &lt;a class="reference external" href="http://django-fr.org"&gt;django-fr&lt;/a&gt; &lt;a class="footnote-reference" href="#id1" id="id2"&gt;[1]&lt;/a&gt; locale à Toulouse&amp;nbsp;:
&lt;a class="reference external" href="http://rencontres.django-fr.org/2012/tolosa/"&gt;DjangoCon Toulouse&lt;/a&gt; &lt;a class="footnote-reference" href="#id3" id="id4"&gt;[2]&lt;/a&gt;. Ce fût une fois de plus une réussite pour ces rencontres
qui sont le point fort de l'année pour notre communauté.&lt;/p&gt;
&lt;div class="section" id="pardon"&gt;
&lt;h2&gt;Pardon&lt;/h2&gt;
&lt;p&gt;Je tiens tout d'abord à m'excuser pour deux points en particulier …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Ce 24 et 25 novembre se tenait la rencontre &lt;a class="reference external" href="http://django-fr.org"&gt;django-fr&lt;/a&gt; &lt;a class="footnote-reference" href="#id1" id="id2"&gt;[1]&lt;/a&gt; locale à Toulouse&amp;nbsp;:
&lt;a class="reference external" href="http://rencontres.django-fr.org/2012/tolosa/"&gt;DjangoCon Toulouse&lt;/a&gt; &lt;a class="footnote-reference" href="#id3" id="id4"&gt;[2]&lt;/a&gt;. Ce fût une fois de plus une réussite pour ces rencontres
qui sont le point fort de l'année pour notre communauté.&lt;/p&gt;
&lt;div class="section" id="pardon"&gt;
&lt;h2&gt;Pardon&lt;/h2&gt;
&lt;p&gt;Je tiens tout d'abord à m'excuser pour deux points en particulier&amp;nbsp;:&lt;/p&gt;
&lt;div class="section" id="l-oubli"&gt;
&lt;h3&gt;L'oubli&lt;/h3&gt;
&lt;p&gt;J'ai un cerveau farceur. Malgré le mail des gentils organisateurs me disant que
ma présentation ET mon &lt;em&gt;lightning talk&lt;/em&gt; étaient acceptés, je n'ai retenu que la
première partie.&lt;/p&gt;
&lt;p&gt;Je me suis donc retrouvé le matin même, à quelques minutes de
mon LT, à me rendre compte qu'en fait, je devais parler deux fois dans la
journée, et que je n'avais strictement rien préparé pour la présentation
courte.&lt;/p&gt;
&lt;p&gt;Panique, stress, déception, et surtout honte&amp;nbsp;: non seulement l'auditoire allait
devoir subir mon absence de préparation (et de slides), mais d'autres
propositions avaient été refusées pour que je puisse faire la mienne.&lt;/p&gt;
&lt;p&gt;Double manque de respect.&lt;/p&gt;
&lt;p&gt;La preuve, si il en fallait une, que j'avais vraiment complètement occulté
cette présentation courte, c'est que j'avais fait il y a quelques temps un
article sur mon blog pour en parler&amp;nbsp;: &lt;a class="reference external" href="//mathieu.agopian.info/blog/vim-screen-le-pair-prog-des-champions.html"&gt;Vim + Screen&amp;nbsp;: le pair-prog des
champions&lt;/a&gt; &lt;a class="footnote-reference" href="#id5" id="id6"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-phrase-malheureuse"&gt;
&lt;h3&gt;La phrase malheureuse&lt;/h3&gt;
&lt;p&gt;Lors de ma présentation sur &lt;a class="reference external" href="http://mathieu.agopian.info/presentations/2012_djangocon_toulouse/"&gt;Django pour les fainéants, le retour&lt;/a&gt; &lt;a class="footnote-reference" href="#id7" id="id8"&gt;[4]&lt;/a&gt;, j'ai voulu
faire comprendre que, à mon avis, respecter la &lt;a class="reference external" href="http://www.python.org/dev/peps/pep-0008/"&gt;pep8&lt;/a&gt; &lt;a class="footnote-reference" href="#id9" id="id10"&gt;[5]&lt;/a&gt; faisait partie des bases.&lt;/p&gt;
&lt;p&gt;Je l'ai très maladroitement exprimé en disant quelque chose du genre&amp;nbsp;:&lt;/p&gt;
&lt;p&gt;«&amp;nbsp;Tout le monde connaît la pep8 j'espère&amp;nbsp;! Sinon, je suis prêt à lancer des
craies dans la salle hein&amp;nbsp;!&amp;nbsp;»&lt;/p&gt;
&lt;p&gt;C'est ce genre de phrase, je pense, qui contribuent et renforcent le sentiment
d'élitisme que peuvent ressentir les nouveaux venus. J'en reparlerais un peu
plus loin dans ce billet.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="le-kif"&gt;
&lt;h2&gt;Le kif&lt;/h2&gt;
&lt;p&gt;Autant le dire tout de suite, j'ai &lt;em&gt;kiffé&lt;/em&gt;. Comme un &lt;em&gt;djeunz&lt;/em&gt;. L'apéro du
vendredi soir était très convivial, et m'a permis de croiser des têtes connues
ailleurs (ping &lt;a class="reference external" href="http://sudweb.fr"&gt;Sudweb&lt;/a&gt; &lt;a class="footnote-reference" href="#id11" id="id12"&gt;[6]&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Je ne suis pas persuadé que le format «&amp;nbsp;on se rencontre dans un bar&amp;nbsp;» soit
idéal, parce que c'est en général très très bruyant, bondé, la musique trop
forte, et on s'entend pas hurler à 20cm l'un de
l'autre.&lt;/p&gt;
&lt;p&gt;C'est peut-être juste mon âge avancé ;)&lt;/p&gt;
&lt;p&gt;La journée de samedi s'est passé sans soucis, j'espère que les participants au
repas de midi ont apprécié le fondant au chocolat que j'ai aidé &lt;a class="reference external" href="http://blog.mathieu-leplatre.info/pages/about.html"&gt;Mathieu&lt;/a&gt; &lt;a class="footnote-reference" href="#id13" id="id14"&gt;[7]&lt;/a&gt; à
faire (j'ai cassé les carreau de chocolat et j'ai beurré le moule, ça compte
non&amp;nbsp;?)&lt;/p&gt;
&lt;p&gt;Autant les conférences courtes que les longues ont été de très bon niveau, j'ai
appris énormément de choses, et c'est tant mieux, pourvu que ça dure&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;C'est agréable de voir des retours d'utilisation, parfois dans de grandes
boites dont on avait même pas eu vent jusque là.&lt;/p&gt;
&lt;p&gt;Le samedi soir était très sympa, et surtout l'endroit était assez calme pour
être propice aux discussions et échanges. Je trouve ça très utile d'avoir des
&lt;em&gt;temps morts&lt;/em&gt; pour permettre justement aux gens de se rencontrer (après tout,
ça s'appelle bien les «&amp;nbsp;rencontres Django-fr&amp;nbsp;»).&lt;/p&gt;
&lt;p&gt;Le dimanche avec ses ateliers semi-improvisés étaient je pense un des moments
privilégiés pour les rencontres, la possibilité d'échanger directement avec ses
pairs sur différents sujets, proposer de l'aide aux nouveaux venus, recevoir
des conseils de &lt;em&gt;vieux briscards&lt;/em&gt;, échanger sur de bonnes pratiques, ou tout
simplement faire connaissance avec de nouveaux visages.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pistes-d-amelioration"&gt;
&lt;h2&gt;Pistes d'amélioration&lt;/h2&gt;
&lt;p&gt;En parlant de faire connaissance avec de nouveaux visages, je pense qu'il
serait enrichissant et souhaitable que les &lt;strong&gt;rencontres&lt;/strong&gt; Django-fr aident
justement à faire des rencontres. Je pense que le format actuel est bon, mais
j'aimerais exhorter mes collègues, amis, connaissances actuelles à rencontrer
et faire connaissance avec les nouveaux venus.&lt;/p&gt;
&lt;p&gt;Il est naturel de se diriger par défaut vers ses amis et connaissances pour
échanger des nouvelles, passer du bon temps ensemble... mais cela renforce le
sentiment d'exclusion et d'élitisme qui peut être ressenti par les nouveaux
venus.&lt;/p&gt;
&lt;p&gt;Merci d'ailleurs &lt;a class="reference external" href="https://twitter.com/FabriceBent"&gt;Fabrice&lt;/a&gt; &lt;a class="footnote-reference" href="#id16" id="id17"&gt;[8]&lt;/a&gt; d'avoir osé prendre la parole pour faire part de ta
crainte de participer et de poser des questions en étant débutant.&lt;/p&gt;
&lt;p&gt;Il est de notre devoir à tous, et je ne parle pas des organisateurs qui ont
déjà la vie dure pour faire en sorte que l'événement en lui-même se passe bien,
de garder un esprit ouvert et d'accueil.&lt;/p&gt;
&lt;p&gt;Je reviens sur ma phrase malheureuse, et c'est typiquement le genre de petite
blague qui peut conforter un débutant dans l'idée qu'il ferait mieux de se
taire et de ne pas poser de questions tant qu'il n'est pas un peu plus au fait
des us et coutumes de la communauté.&lt;/p&gt;
&lt;p&gt;Après tout, c'était d'autant plus maladroit que j'ai moi-même utilisé Python
pendant de nombreuses années avant même de connaître la pep8 (et je ne parle
pas de l'appliquer automatiquement dans tous mes projets).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Je terminerais en remerciant très chaleureusement &lt;a class="reference external" href="http://blog.mathieu-leplatre.info/pages/about.html"&gt;Mathieu&lt;/a&gt; &lt;a class="footnote-reference" href="#id13" id="id15"&gt;[7]&lt;/a&gt;, &lt;a class="reference external" href="https://twitter.com/zebuline"&gt;Lauréline&lt;/a&gt; &lt;a class="footnote-reference" href="#id18" id="id19"&gt;[9]&lt;/a&gt;, &lt;a class="reference external" href="http://www.marmelune.net/"&gt;Benoit&lt;/a&gt; &lt;a class="footnote-reference" href="#id20" id="id21"&gt;[10]&lt;/a&gt;
et &lt;a class="reference external" href="https://twitter.com/duboisnicolas"&gt;Nicolas&lt;/a&gt; &lt;a class="footnote-reference" href="#id22" id="id23"&gt;[11]&lt;/a&gt; pour l'organisation de cet événement.&lt;/p&gt;
&lt;p&gt;C'est grâce à des gens comme vous qu'on peut continuer à se rencontrer, à
échanger, et passer un si bon moment.&lt;/p&gt;
&lt;p&gt;Encore félicitations pour ce week-end qui se sera écoulé bien trop vite, et
vivement la &lt;a class="reference external" href="http://forum.django-fr.org/viewtopic.php?id=1306"&gt;prochaine rencontre&lt;/a&gt; &lt;a class="footnote-reference" href="#id24" id="id25"&gt;[12]&lt;/a&gt; !&lt;/p&gt;
&lt;p&gt;Ajout (2012-12-10)&amp;nbsp;: les vidéos sont disponibles&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="http://mathieu.agopian.info/presentation_videos/agopian-pair-programming.mp4"&gt;Vidéo de Screen + vim : le pair programming des champions&lt;/a&gt; &lt;a class="footnote-reference" href="#id26" id="id27"&gt;[13]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://mathieu.agopian.info/presentation_videos/agopian-django-pour-les-faineants.mp4"&gt;Vidéo de Django pour les fainéants, le retour&lt;/a&gt; &lt;a class="footnote-reference" href="#id28" id="id29"&gt;[14]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="id1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id2"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://django-fr.org"&gt;http://django-fr.org&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id4"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://rencontres.django-fr.org/2012/tolosa/"&gt;http://rencontres.django-fr.org/2012/tolosa/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id6"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="//mathieu.agopian.info/blog/vim-screen-le-pair-prog-des-champions.html"&gt;|filename|vim-screen-le-pair-prog-des-champions.rst&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id7" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id8"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://mathieu.agopian.info/presentations/2012_djangocon_toulouse/"&gt;http://mathieu.agopian.info/presentations/2012_djangocon_toulouse/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id9" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id10"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://www.python.org/dev/peps/pep-0008/"&gt;http://www.python.org/dev/peps/pep-0008/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id11" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id12"&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://sudweb.fr"&gt;http://sudweb.fr&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id13" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[7]&lt;/td&gt;&lt;td&gt;&lt;em&gt;(&lt;a class="fn-backref" href="#id14"&gt;1&lt;/a&gt;, &lt;a class="fn-backref" href="#id15"&gt;2&lt;/a&gt;)&lt;/em&gt; &lt;a class="reference external" href="http://blog.mathieu-leplatre.info/pages/about.html"&gt;http://blog.mathieu-leplatre.info/pages/about.html&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id16" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id17"&gt;[8]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://twitter.com/FabriceBent"&gt;https://twitter.com/FabriceBent&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id18" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id19"&gt;[9]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://twitter.com/zebuline"&gt;https://twitter.com/zebuline&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id20" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id21"&gt;[10]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://www.marmelune.net/"&gt;http://www.marmelune.net/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id22" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id23"&gt;[11]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://twitter.com/duboisnicolas"&gt;https://twitter.com/duboisnicolas&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id24" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id25"&gt;[12]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://forum.django-fr.org/viewtopic.php?id=1306"&gt;http://forum.django-fr.org/viewtopic.php?id=1306&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id26" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id27"&gt;[13]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://mathieu.agopian.info/presentation_videos/agopian-pair-programming.mp4"&gt;http://mathieu.agopian.info/presentation_videos/agopian-pair-programming.mp4&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id28" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id29"&gt;[14]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://mathieu.agopian.info/presentation_videos/agopian-django-pour-les-faineants.mp4"&gt;http://mathieu.agopian.info/presentation_videos/agopian-django-pour-les-faineants.mp4&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="django"></category></entry><entry><title>Taxonomie des entreprises</title><link href="//mathieu.agopian.info/blog/taxonomie-des-entreprises.html" rel="alternate"></link><published>2012-10-03T17:40:00+02:00</published><updated>2012-10-03T17:40:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2012-10-03:/blog/taxonomie-des-entreprises.html</id><summary type="html">&lt;p&gt;Nous avons vu dans un &lt;a class="reference external" href="//mathieu.agopian.info/blog/plan-de-carriere-dun-developpeur.html"&gt;précédent billet&lt;/a&gt; qu'il existait deux principales
échelles qu'il était possible de gravir dans la carrière d'un développeur.&lt;/p&gt;
&lt;p&gt;Ces deux échelles permettent d'accéder à des types d'entreprise très
différents.&lt;/p&gt;
&lt;p&gt;Nous allons nous essayer à une taxonomie des entreprises, à une classification
selon une liste de critères …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Nous avons vu dans un &lt;a class="reference external" href="//mathieu.agopian.info/blog/plan-de-carriere-dun-developpeur.html"&gt;précédent billet&lt;/a&gt; qu'il existait deux principales
échelles qu'il était possible de gravir dans la carrière d'un développeur.&lt;/p&gt;
&lt;p&gt;Ces deux échelles permettent d'accéder à des types d'entreprise très
différents.&lt;/p&gt;
&lt;p&gt;Nous allons nous essayer à une taxonomie des entreprises, à une classification
selon une liste de critères.&lt;/p&gt;
&lt;p&gt;Attention, ce billet est mon point de vue personnel. Ne le prenez donc pas au
pied de la lettre, mais plutôt comme un guide pour évaluer une entreprise, pour
choisir parmi différents types d'entreprises, celui qui correspondra le plus à
l'échelle qu'on a choisi de gravir.&lt;/p&gt;
&lt;p&gt;Je vais essayer de rester le plus objectif possible dans l'énoncé des critères,
sans laisser transparaître de jugement. En effet, il vous appartient de définir
pour chacun des critères, ce qui correspond le plus à vos attentes, à vos
souhaits.&lt;/p&gt;
&lt;p&gt;Ce billet est long, très très long.&lt;/p&gt;
&lt;div class="section" id="les-criteres"&gt;
&lt;h2&gt;Les critères&lt;/h2&gt;
&lt;p&gt;Je vois plusieurs critères qui peuvent jouer sur le choix d'une entreprise
lorsqu'un développeur est en recherche d'un emploi, de nouveaux horizons, voire
même de créer sa boite&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;la taille et répartition géographique&amp;nbsp;: nombre d'employés, entreprise locale,
nationale, internationale&lt;/li&gt;
&lt;li&gt;la visibilité/réputation&amp;nbsp;: quelle image est-ce qu'elle véhicule, quel domaine&lt;/li&gt;
&lt;li&gt;le rapport à l'open source&amp;nbsp;: pour, contre, ne se prononce pas, pratique ou
non&lt;/li&gt;
&lt;li&gt;les perspectives d'évolution&amp;nbsp;: nulles, à l'ancienneté, changement de métier,
d'expertise, nécessaire pour évoluer&lt;/li&gt;
&lt;li&gt;la paie&lt;/li&gt;
&lt;li&gt;technologie&amp;nbsp;: à la pointe ou pas, imposée ou non&lt;/li&gt;
&lt;li&gt;l'environnement de travail&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="taille-et-repartition-geographique"&gt;
&lt;h2&gt;Taille et répartition géographique&lt;/h2&gt;
&lt;p&gt;Plus une entreprise est grande (en nombre d'employés), et avec une répartition
géographique large, plus il y a de risques de se retrouver comme un numéro, un
employé de plus.&lt;/p&gt;
&lt;p&gt;La contrepartie, est de bénéficier de meilleurs avantages&amp;nbsp;: comité d'entreprise
plein aux as, plus de marge pour évoluer, changer de service, «&amp;nbsp;monter en
grade&amp;nbsp;» (diriger une équipe, nous y reviendrons). Et si il y a des plans
sociaux, les indemnités sont en général bien plus importantes, et accompagnées
de propositions de requalification, de formation. Il y a aussi un sentiment de
sécurité, en se disant que l'entreprise est «&amp;nbsp;too big to fail&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;Au contraire, dans une entreprise de petite taille, il est plus facile de
compter, de faire une différence, de se sentir un maillon indispensable de la
vie de l'entreprise. Il est aussi plus facile de communiquer sur ce qu'on fait
au quotidien, seul ou en équipe, sur sa valeur ajoutée.&lt;/p&gt;
&lt;p&gt;Il est par contre plus difficile de négocier une augmentation, un changement de
statut, un aménagement du temps de travail, ou toute autre chose qui pourrait
entraver la progression de l'entreprise. Mais en général la négociation se fait
directement avec l'employeur, et non au travers d'une équipe de ressources
humaines.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="visibilite-et-reputation"&gt;
&lt;h2&gt;Visibilité et réputation&lt;/h2&gt;
&lt;p&gt;Est-ce qu'on préfère une entreprise dynamique&amp;nbsp;? Stable&amp;nbsp;? Historique ou jeune et
innovante&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Est-ce qu'on privilégie une entreprise dotée d'une aura de bienfaisance, comme
une entreprise qui travaille pour des ONG, pour de l'humanitaire, ou plutôt une
entreprise qui représente le pouvoir, financier par exemple, comme une place
des marché, une banque, un assureur...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rapport-a-l-open-source"&gt;
&lt;h2&gt;Rapport à l'open-source&lt;/h2&gt;
&lt;p&gt;Si on cherche à gravir l'échelle de la visibilité, il est important de
privilégier une entreprise qui le permette, voire qui l'encourage.&lt;/p&gt;
&lt;p&gt;Dans ce domaine, les entreprises peuvent être très différentes&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;prédateur&amp;nbsp;: utilise au quotidien des logiciels, serveurs, librairies...
open-source, mais ne contribue pas, et interdit à ses employés de contribuer
sur le temps de travail, ou sur du code développé par/pour l'entreprise.&lt;/li&gt;
&lt;li&gt;neutre&amp;nbsp;: utilise, mais contribue aussi à l'occasion. À l'occasion de la
découverte d'un bug dans une application, ou de la création d'une nouvelle
librairie, le développeur est autorisé, voire encouragé, à contribuer, y
compris sur son temps de travail pour l'entreprise.&lt;/li&gt;
&lt;li&gt;moteur&amp;nbsp;: le cœur de métier de l'entreprise est basé sur un logiciel qui est
en open-source, par exemple &lt;a class="reference external" href="http://hybird.org/"&gt;Hybird&lt;/a&gt; avec son CRM Crème. Ou encore &lt;a class="reference external" href="http://mozilla.org"&gt;Mozilla&lt;/a&gt;
qui a pour crédo «&amp;nbsp;we develop in the open&amp;nbsp;».&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Surtout, se méfier comme de la peste des entreprises qui demandent une forte
visibilité lors du recrutement (réclament le compte
github/bitbucket/stackoverflow), mais ne contribuent pas, ni ne permettent à
leurs employés de contribuer. Ces entreprises profitent de l'open-source, sans
qu'il y puisse y avoir de retour. De plus, lorsqu'un développeur voudra la
quitter, et trouver un autre employeur, il ne pourra plus présenter de projet
récent sur lequel il aura pu travailler.&lt;/p&gt;
&lt;p&gt;Oui, il est possible de contribuer et de créer sur son temps libre, mais
combien d'heures arrivez-vous à libérer pour ça par semaine, en respectant un
bon équilibre travail-vie, en accordant du temps à votre famille et à vous
même&amp;nbsp;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="perspectives-d-evolution"&gt;
&lt;h2&gt;Perspectives d'évolution&lt;/h2&gt;
&lt;p&gt;De la même manière, on voit clairement  l'intérêt d'avoir des perspectives
d'évolution quand on veut gravir l'échelle de l'entreprise.&lt;/p&gt;
&lt;p&gt;Il est en effet important de pouvoir changer de poste, monter en grade, diriger
une équipe, prendre des responsabilités, dans l'optique de pouvoir se
«&amp;nbsp;vendre&amp;nbsp;» lors d'un entretien d'embauche ultérieur.&lt;/p&gt;
&lt;p&gt;Tout comme il faut se méfier des entreprises prédatrices de visibilité, il faut
se méfier des entreprises qui vont vous demander de justifier de
responsabilités ou de management dans votre précédent emploi, pour ensuite vous
cantonner dans un poste sans possibilité d'évoluer.&lt;/p&gt;
&lt;p&gt;J'ai vécu (très brièvement) cette expérience dans une boite qui promettait
monts et merveilles, mais une fois embauché, j'ai vite ressenti le «&amp;nbsp;plafond de
verre&amp;nbsp;», celui qui délimite les sous-fifres du dessous, des chefs et patrons du
dessus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="paie"&gt;
&lt;h2&gt;Paie&lt;/h2&gt;
&lt;p&gt;Aspect non négligeable, quoi qu'on en dise, et ce pour au moins deux raisons&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;le salaire, c'est en grande partie la valorisation du travail qui est fait&lt;/li&gt;
&lt;li&gt;un salaire suffisant permet de se concentrer sur son travail, sans avoir à
penser à faire un deuxième travail, s'établir en auto-entrepreneur...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Attention sur ce point glissant. Obtenir un meilleur salaire, c'est aussi subir
une plus grosse pression sociale. Si on est mieux payé que X, alors il faut
apporter plus de valeur que X. Il n'est pas confortable d'être la personne la
mieux payée.&lt;/p&gt;
&lt;p&gt;Par ailleurs, un fort salaire peut entraîner un niveau de vie élevé, qui sera
comme une prison dorée&amp;nbsp;: il est très facile d'augmenter son niveau de vie, mais
le réduire c'est &lt;a class="reference external" href="http://mathieu.agopian.info/mnmlist"&gt;une autre paire de manches&lt;/a&gt;. Et trouver une entreprise qui
corresponde à ses critères, ET qui propose un salaire au moins aussi élevé,
c'est beaucoup plus ardu.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="technologie"&gt;
&lt;h2&gt;Technologie&lt;/h2&gt;
&lt;p&gt;La encore, deux écoles.&lt;/p&gt;
&lt;p&gt;Je ne le cache pas, je suis fan de Python, de Clojure, et d'autres technos
funs et à la pointe. Par contre, je sais que ce n'est pas dans ce domaine que
je trouverai le plus de propositions de travail.&lt;/p&gt;
&lt;p&gt;Java, PHP sont eux très largement répandus, très utilisés, et donc entraînent
automatiquement une très forte demande.&lt;/p&gt;
&lt;p&gt;Mais sans même parler de langages, certaines entreprises travaillent et
imposent certaines technologies, certains &lt;em&gt;frameworks&lt;/em&gt;, librairies, outils,
éditeurs.&lt;/p&gt;
&lt;p&gt;Quelle est la place laissée aux expérimentations sur d'autres technologies&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Est-ce que le but est de devenir un expert très pointu sur une technologie
précise, ou plutôt un couteau suisse, un touche à tout, un polyvalent&amp;nbsp;?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="environnement-de-travail"&gt;
&lt;h2&gt;Environnement de travail&lt;/h2&gt;
&lt;p&gt;Le plus gros morceau pour la fin. Ce critère regroupe plusieurs sous critères,
qui peuvent bien entendu être séparés en critères à part entière selon leur
importance aux yeux du chercheur d'emploi.&lt;/p&gt;
&lt;div class="section" id="travail-seul-ou-en-equipe"&gt;
&lt;h3&gt;Travail seul ou en équipe&lt;/h3&gt;
&lt;p&gt;Quelle est votre préférence&amp;nbsp;? Un dicton malien dit «&amp;nbsp;seul on va plus vite,
ensemble on va plus loin.&amp;nbsp;»&lt;/p&gt;
&lt;p&gt;C'est vraiment une question de goût. Seul, on est plus libre de son travail,
mais on est aussi plus sous pression, sans aide, sans recours ni soutient. À
plusieurs, on est plus confortable, on peut s'entraider, un sentiment
d'émulation peut se mettre en place, une saine compétition.
Il y a les revues de code, les discussions au coin de la machine à café, ou
autour d'une bière, les coups de main dans les moments de stress ou de
détresse, les conseils avisés, la possibilité de faire du &lt;a class="reference external" href="//mathieu.agopian.info/blog/vim-screen-le-pair-prog-des-champions.html"&gt;pair-programming&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mais il faut travailler en équipe, et c'est parfois compliqué. Il faut arriver
à suivre le travail de chacun, se tenir au courant des modifications, des
ajouts de fonctionnalité, il faut négocier, convaincre, écouter, accepter,
partager.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="materiel"&gt;
&lt;h3&gt;Matériel&lt;/h3&gt;
&lt;p&gt;Ce point est important. Il suffit, pour s'en convaincre, d'écouter l'histoire
d'un développeur anonyme&amp;nbsp;: « On m'avait promis un ordinateur portable et un
bureau à ma taille. Le premier jour, lors de mon arrivée dans le bureau, j'ai
vu mes collègues ramener une table branlante, un pied ne tenant plus que par
une vis, et le responsable sortir de la salle serveur avec un écran cathodique
14&amp;quot;, un vieux clavier, une souris à boule, et un vieux pentium poussif (un des
serveurs). Trois mois après, à la veille de la fin de ma première période
d'essai, rien n'avait changé, j'ai démissionné.&amp;nbsp;»&lt;/p&gt;
&lt;p&gt;Si vous avez besoin d'une justification comptable pour investir sur du bon
matériel de développement, vous pouvez jeter un œil à l'article &lt;a class="reference external" href="http://thecodersbreakfast.net/index.php?post/2012/08/26/equipez-vos-d%C3%A9veloppeurs"&gt;équipez vos
développeurs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et sachez, patrons, chefs d'équipe, ressources humaines, ou qui que ce soit qui
gère le matériel dans votre entreprise, que ça peut être très humiliant de
devoir quémander pour avoir un outil de travail correct. Un chauffeur de taxi
ne voudrait pas passer sa journée dans une vieille guimbarde, de la même
manière, un développeur ne supportera pas du matériel obsolète, lent, une
chaise mal adaptée (pour ceux qui travaillent encore assis ;), un écran de
mauvaise qualité...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="perception-de-la-valeur-d-un-developpeur"&gt;
&lt;h3&gt;Perception de la valeur d'un développeur&lt;/h3&gt;
&lt;p&gt;Quelle est la perception de la valeur d'un développeur dans l'entreprise&amp;nbsp;?
Est-ce qu'il est perçu comme un technicien, un exécutant&amp;nbsp;? Est-ce que le patron
éclate de rire quand on lui explique que le développement, ça s'apparente à un
métier créatif&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Là encore, croyez-en l'expérience d'un développeur pas si anonyme qui a testé
pour vous, le patron rigoleur de la vieille école, mais aussi la grosse boite
qui classe par ordre d'importance dans la boite&amp;nbsp;:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;le patron (logique)&lt;/li&gt;
&lt;li&gt;les électroniciens&lt;/li&gt;
&lt;li&gt;les opticiens&lt;/li&gt;
&lt;li&gt;le bureau d'étude&lt;/li&gt;
&lt;li&gt;l'administration&lt;/li&gt;
&lt;li&gt;les secrétaires&lt;/li&gt;
&lt;li&gt;les développeurs&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On obtient alors des choses intéressantes, comme par exemple des ordinateurs
trop vieux et obsolètes pour les secrétaires, qui sont offerts au développeurs.
Ou encore une négociation de salaire fort surprenante.&lt;/p&gt;
&lt;p&gt;Détrompez-vous, ce ne sont pas des cas isolés, et on retrouve malheureusement
souvent, surtout en France, surtout dans les boites dirigées par des patrons de
la vieille école (comprendre, ceux qui se sont fait avant l'apparition de
l'informatique utilisée couramment dans les entreprises), ce genre de
réactions.&lt;/p&gt;
&lt;p&gt;Et encore plus souvent, le discours suivant&amp;nbsp;: «&amp;nbsp;Des développeurs, on en trouve
à la pelle à notre époque. Demain, je peux trouver n'importe quel nouveau
diplômé tout frai émoulu, payé une fraction de ton salaire, et qui fera au
moins aussi bien que toi.&amp;nbsp;»&lt;/p&gt;
&lt;p&gt;Ou encore&amp;nbsp;: «&amp;nbsp;À ton âge, c'est dommage que tu sois encore développeur. Il est
temps pour toi d'évoluer, de devenir chef, de diriger une équipe, ou au moins
de devenir chef de projet, ou consultant technique. Tu as du potentiel, c'est
dommage de le gâcher en restant prostré derrière ton écran toute la journée.
C'est bon pour les juniors ça.&amp;nbsp;»&lt;/p&gt;
&lt;p&gt;Non, un développeur n'a pas forcément pour vocation de diriger une équipe.
C'est un autre métier. De même pour chef de projet, c'est un autre métier. Je
ne parle même pas de «&amp;nbsp;technical consultant&amp;nbsp;» ou autre «&amp;nbsp;technico-commercial&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;Un développeur peut tout à fait souhaiter devenir un bon développeur, puis un
excellent développeur, et de se perfectionner jour après jour. On trouve
facilement (à l'étranger, aux USA en particulier) des développeurs bien mieux
payés que leurs chef d'équipe ou chef de projet. Et ça paraît logique, vu que
ce sont des métiers différents.&lt;/p&gt;
&lt;p&gt;On imagine pas proposer à un mécanicien d'évoluer pour devenir charcutier, ou à
un dentiste d'évoluer pour devenir boulanger. Ce sont des métiers différents.
Qui demandent des qualités différentes.&lt;/p&gt;
&lt;p&gt;Petite astuce&amp;nbsp;: en général, une entreprise qui a un blog technique (comme
&lt;a class="reference external" href="http://tech.novapost.fr/"&gt;celui de Novapost&lt;/a&gt; par exemple), qui sponsorise des évènements et conférences
pour développeurs, qui encourage ses employés à participer à des &lt;em&gt;sprints&lt;/em&gt; (en
leur offrant les jours de congé)... c'est une entreprise qui accorde une grande
valeur à ses développeurs. C'est bon signe&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="prodonfeur-hauteur-de-la-hierarchie"&gt;
&lt;h3&gt;Prodonfeur/hauteur de la hiérarchie&lt;/h3&gt;
&lt;p&gt;Plus une entreprise est grande, plus il y a de chances que la hiérarchie soit
très profonde. Qu'on se retrouve avec des chefs, des patrons, des responsables,
des supérieurs, des boss, des n+1, n+2 et &lt;em&gt;tutti quanti&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;D'un côté c'est une opportunité pour quelqu'un qui souhaite prendre des
responsabilités&amp;nbsp;: il y a de la place pour se faire son trou, monter dans la
hiérarchie petit à petit. D'un autre côté, quand on veut apporter un
changement, il faut convaincre non un ou deux supérieurs, mais parfois beaucoup
beaucoup plus.&lt;/p&gt;
&lt;p&gt;Il y a aussi des entreprises qui ont une hiérarchie très profonde/haute (tout
dépends du point de vue), et on se retrouve avec plus de responsables et
décideurs que de faiseurs. Rappelez-vous l'histoire du plafond de verre. Il est
très désagréable de se retrouver troufion aux ordres d'une pléthore de chefs
qui cherchent tous à se faire valoir les uns auprès des autres, qui
s'accaparent chaque once de gloire, chaque idée intéressante, chaque
accomplissement fait «&amp;nbsp;en bas&amp;nbsp;» pour leur propre avancement et mise en avant.&lt;/p&gt;
&lt;p&gt;Même si le grand nombre des niveaux hiérarchiques (rapporté au nombre des
employés) n'implique pas forcément une ambiance désagréable, je pense que ça en
est un bon indicateur, méfiance&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="amenagement-des-horaires"&gt;
&lt;h3&gt;Aménagement des horaires&lt;/h3&gt;
&lt;p&gt;Encore une fois, une question de goût.&lt;/p&gt;
&lt;p&gt;On peut préférer une grande rigueur sur les horaires, ce qui permet d'être sûr
d'avoir les personnes nécessaires à l'avancement de son travail quotidien quand
on est au bureau. Exemple&amp;nbsp;: j'ai besoin d'une nouvelle VM, mais pas de bol, il
n'est «&amp;nbsp;que&amp;nbsp;» 11h, et le responsable ne sera pas là avant une bonne demi-heure.
Ok, il sera là encore très tard ce soir, mais moi, par contre, je serais devant
un bon repas, chez moi, en famille, et pendant ce temps, je suis coincé.&lt;/p&gt;
&lt;p&gt;D'un autre côté, une plus grande possibilité d'aménagement des horaires est
très confortable. Nous ne sommes pas tous productifs au même moment. Je suis du
matin, d'autres sont du début d'après-midi, début de soirée, voire pleine nuit.&lt;/p&gt;
&lt;p&gt;Ah et puis j'ai un enfant à amener et chercher à l'école, déposer au foot, mais
pas de soucis, je rattraperai ce week-end, j'aurai une bonne demi-journée de
disponible quand mes enfants seront chez leurs grands-parents.&lt;/p&gt;
&lt;p&gt;Ou alors je travaille dans le train (1h30 tous les matins, 1h30 tous les
soirs), du coup j'arrive plus tard au travail, et je repars plus tôt, mais je
travaille au moins autant&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="possibilite-de-travailler-a-distance"&gt;
&lt;h3&gt;Possibilité de travailler à distance&lt;/h3&gt;
&lt;p&gt;J'en parlerai plus largement dans un prochain billet, mais tout le monde n'est
pas fait pour le télé-travail.&lt;/p&gt;
&lt;p&gt;Cela mis à part, si on souhaite pouvoir travailler à distance un jour ou deux
jours par semaine, voire à 100%, est-ce possible&amp;nbsp;? Est-ce que l'entreprise a
l'habitude, et met en place les outils nécessaires pour ça&amp;nbsp;?&lt;/p&gt;
&lt;p&gt;Est-ce qu'elle organise des rencontres et réunions tout au long de l'année pour
permettre aux employés travaillant à distance et sur place de se rencontrer,
permettre à la cohésion d'équipe de se mettre en place&amp;nbsp;?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="utilisation-des-criteres-pour-classifier-une-entreprise"&gt;
&lt;h2&gt;Utilisation des critères pour classifier une entreprise&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://lincolnloop.com/about/yml/"&gt;Yann Malet&lt;/a&gt; m'a donné une excellente idée&amp;nbsp;: utiliser des &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Radar_chart"&gt;diagrammes radar&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tout d'abord, faire une liste des critères qui paraissent importants, et les
nommer «&amp;nbsp;dans le sens où ils sont importants&amp;nbsp;». Je vais faire l'exercice pour
que vous compreniez bien&amp;nbsp;:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;petitesse de l'entreprise&amp;nbsp;: je préfère en général une entreprise à taille
humaine. Une trentaine de personnes me paraît être un idéal.&lt;/li&gt;
&lt;li&gt;bonne visibilité et réputation&amp;nbsp;: si possible une entreprise qui apporte une
vraie valeur, qui fait une différence dans la vie des gens&lt;/li&gt;
&lt;li&gt;acteur clé de l'open source&amp;nbsp;: j'accorde beaucoup d'importance à
l'open-source, et je veux pouvoir passer du temps à y contribuer&lt;/li&gt;
&lt;li&gt;bonne paie&lt;/li&gt;
&lt;li&gt;technologie à la pointe&amp;nbsp;: possibilité d'expérimenter de nouvelles
technologies&lt;/li&gt;
&lt;li&gt;bon environnement de travail&amp;nbsp;: travail en équipe, matériel correct,
aménagement des horaires&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans mon cas, je ne sélectionne aucun des critères suivants&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;télé-travail&amp;nbsp;: ce n'est pas un critère que je veux utiliser pour classer les
entreprises, c'est un critère indispensable.&lt;/li&gt;
&lt;li&gt;perception de la valeur d'un développeur&amp;nbsp;: idem, je ne veux pas (plus)
travailler pour une entreprise qui ne comprends pas les spécificités du
développement.&lt;/li&gt;
&lt;li&gt;Perspectives d'évolution&amp;nbsp;: je n'y attache plus d'importance, sachant
maintenant, après avoir fait mes expériences, que ce qui me plait, c'est le
développement.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Il ne reste alors plus qu'à en faire un diagramme radar (j'utilise
LibreOffice). Voilà celui que j'ai fait pour &lt;a class="reference external" href="http://novapost.fr"&gt;Novapost&lt;/a&gt;, mon employeur
actuel&amp;nbsp;:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Diagramme radar pour Novapost" src="images/novapost.png" /&gt;
&lt;p class="caption"&gt;Diagramme radar pour Novapost&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;On peut voir que Novapost correspond vraiment bien à mes critères, ce qui est
une chance inouïe, pour plusieurs raisons&amp;nbsp;:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;je ne connaissais pas Novapost avant d'avoir l'opportunité d'y travailler&lt;/li&gt;
&lt;li&gt;je n'avais pas pris conscience des échelles ni des critères au moment d'y
entrer&lt;/li&gt;
&lt;li&gt;je n'ai pas vraiment gravi la bonne échelle pour y entrer. J'étais vraiment
sur l'échelle de l'entreprise, or j'ai été embauché principalement grâce à
ma visibilité (j'avais fait la connaissance des membres de l'équipe,
&lt;a class="reference external" href="http://twitter.com/coulix"&gt;coulix&lt;/a&gt; et &lt;a class="reference external" href="http://twitter.com/zebuline"&gt;zebuline&lt;/a&gt;, lors des conférences &lt;a class="reference external" href="http://rencontres.django-fr.org"&gt;djangocong&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="section" id="les-cas-particuliers"&gt;
&lt;h2&gt;Les cas particuliers&lt;/h2&gt;
&lt;p&gt;Parlons des inclassables. Des entreprises atypiques, qui se classent plus haut
que le maximum sur un critère par exemple&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/blog"&gt;github&lt;/a&gt;, connu et reconnu pour son turnover inexistant, et la maximisation du
bien-être de ses employés (voir pour cela quelques &lt;a class="reference external" href="http://zachholman.com/talks"&gt;présentations de Zach
Holman&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://lincolnloop.com/blog/"&gt;Lincoln Loop&lt;/a&gt;, la boite trop cool qui met en place des techniques
révolutionnaires. J'en veux pour preuve certains de leurs articles que je
vous recommande pour leur inspiration&amp;nbsp;: &lt;a class="reference external" href="http://lincolnloop.com/blog/2012/aug/20/distributed-workplace/"&gt;The Distributed Workplace&lt;/a&gt;,
&lt;a class="reference external" href="http://lincolnloop.com/blog/2012/jun/15/optimize-motivation/"&gt;Optimize For Motivation&lt;/a&gt;, &lt;a class="reference external" href="http://lincolnloop.com/blog/2012/may/31/lincoln-loop-everyone-sets-their-own-salary/"&gt;Everyone Sets Their Own Salary&lt;/a&gt;, &lt;a class="reference external" href="http://lincolnloop.com/blog/2012/may/21/open-book-finances/"&gt;Open Book
Finances&lt;/a&gt;. Il y a aussi une foule d'articles techniques de bonne qualité.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://mozilla.org"&gt;Mozilla&lt;/a&gt;, cette immense organisation, répartie sur tous les continents, qui a
pour but de libérer les usages et les utilisateurs, de fournir du choix, des
outils de qualité, et qui a pris le parti de tout faire publiquement, que ce
soit les réunions d'équipe accessibles à tout le monde ou tout leur code en
open source.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://scopyleft.fr/"&gt;Scopyleft&lt;/a&gt;, cette SCOP en cours de création par plusieurs amis, basée sur &lt;a class="reference external" href="https://larlet.fr/david/thoughts/#cooperating"&gt;la
coopération&lt;/a&gt; et le partage (on y revient ;). J'ai entendu parler de cette
initiative il y a quelques temps maintenant par &lt;a class="reference external" href="https://nicolas.perriault.net/"&gt;Nicolas Perriault&lt;/a&gt;, et
depuis, je ronge mon frein et trépigne d'impatience de voir ce projet prendre
son essor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quand on postule pour ce genre de boites, c'est plus par coup de cœur que suite
à une réflexion basée sur une classification et la mise en place de critères.
C'est parce que le crédo, l'état d'esprit, la cause, la réputation résonne
directement avec nos attentes.&lt;/p&gt;
&lt;p&gt;Les critères s'effacent devant un critère en particulier&amp;nbsp;: l'environnement de
travail et la liberté des horaires pour github, la perception de la valeur d'un
développeur pour Lincoln Loop, l'open source et la réputation pour Mozilla, la
coopération et le partage pour Scopyleft.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;J'ai fait l'impasse sur plusieurs critères, par exemple est-ce que l'entreprise
est à but lucratif ou non (association loi 1901), est-ce qu'elle propose un
produit ou des services, est-ce qu'elle est viable financièrement, ou
uniquement soutenue par des investissements, quel âge à l'entreprise (plus elle
est jeune, plus il y a de risques, mais aussi plus il y a de chances d'avoir
une vraie valeur ajoutée&amp;nbsp;?), etc.&lt;/p&gt;
&lt;p&gt;En construisant et listant la liste de ses critères, on voit aussi quels sont
ceux qu'on laisse de côté, qui ne sont pas importants. Je n'accorde par exemple
plus beaucoup d'importance aux perspectives d'évolution, voulant à tout prix
faire du développement, rester développeur. J'ai pu tester d'autres postes par
le passé, mais j'en suis toujours revenu. J'ai ça dans le sang ;)&lt;/p&gt;
&lt;p&gt;Le but de ce choix de critères, de cette classification, est de pouvoir bien
cerner le type d'entreprise qu'on cherche. Et ensuite, de pouvoir en déduire le
genre d'échelle qu'il faut gravir.&lt;/p&gt;
&lt;p&gt;Dans mon cas, je me rends très clairement compte que je veux travailler pour
des entreprises qui valorisent leurs développeurs, qui contribuent à
l'open-source, et qui fournissent un bon environnement de travail. Et ce sont
des entreprises qui en général recrutent sur la visibilité, beaucoup plus que
sur le CV.&lt;/p&gt;
&lt;p&gt;Et donc après avoir gravi les échelons de l'entreprise pendant de nombreuses
années (2002-2011), je me retrouve à changer d'optique, de point de vue, et de
stratégie. On dit bien que «&amp;nbsp;mieux vaux tard que jamais&amp;nbsp;», mais si partager mon
expérience pouvait servir à d'autres, tant mieux&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;(Qui a dit que &lt;a class="reference external" href="https://larlet.fr/david/thoughts/#sharing"&gt;le partage&lt;/a&gt; était la meilleure des stratégies&amp;nbsp;? :)&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Plan de carrière d'un développeur</title><link href="//mathieu.agopian.info/blog/plan-de-carriere-dun-developpeur.html" rel="alternate"></link><published>2012-10-02T19:53:00+02:00</published><updated>2012-10-02T19:53:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2012-10-02:/blog/plan-de-carriere-dun-developpeur.html</id><summary type="html">&lt;p&gt;Le &lt;em&gt;plan de carrière&lt;/em&gt;, j'en avais très souvent entendu parler, depuis tout
jeune, depuis avant même de savoir que je voulais devenir développeur.&lt;/p&gt;
&lt;p&gt;Puis quand j'ai découvert l'informatique, je m'y suis jeté à corps perdu, et
j'ai rangé cette notion dans un petit tiroir de mon cerveau étiqueté «&amp;nbsp;inutile
pour …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Le &lt;em&gt;plan de carrière&lt;/em&gt;, j'en avais très souvent entendu parler, depuis tout
jeune, depuis avant même de savoir que je voulais devenir développeur.&lt;/p&gt;
&lt;p&gt;Puis quand j'ai découvert l'informatique, je m'y suis jeté à corps perdu, et
j'ai rangé cette notion dans un petit tiroir de mon cerveau étiqueté «&amp;nbsp;inutile
pour un développeur&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;C'était bien évidemment une erreur. Ne pas comprendre un concept n'en fait pas
un concept inutile.&lt;/p&gt;
&lt;p&gt;Je m'adresse ici aux développeurs salariés, et n'aborderai pas la création
d'entreprise, n'y connaissant rien.&lt;/p&gt;
&lt;div class="section" id="le-plan-de-carriere-qu-est-ce-que-c-est"&gt;
&lt;h2&gt;Le plan de carrière, qu'est-ce que c'est&amp;nbsp;?&lt;/h2&gt;
&lt;p&gt;Un plan de carrière, c'est un but à atteindre dans sa vie professionnelle. Ce
but peut être multiple, et j'en vois trois principaux&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;obtenir plus de responsabilités&lt;/li&gt;
&lt;li&gt;devenir un maillon indispensable/incontournable de l'entreprise&lt;/li&gt;
&lt;li&gt;devenir un référent technique&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Chacun de ces buts permet d'atteindre un idéal qui sera différent pour chacun.
Pour l'un ce sera un plus gros salaire, pour l'autre plus de temps libre, plus
d'importance (l'entreprise ne pourrait pas tourner sans moi), plus de poids
social (je suis le chef de x personnes), plus de sécurité de l'emploi.&lt;/p&gt;
&lt;p&gt;Pour atteindre ces buts, il faudra alors utiliser une ou plusieurs échelles,
pour reprendre l'image assez répandue de «&amp;nbsp;gravir les échelons&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;Pour un développeur, je perçois deux échelles fondamentalement différentes, que
je vais tenter de vous décrire.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="l-entreprise"&gt;
&lt;h2&gt;L'entreprise&lt;/h2&gt;
&lt;p&gt;L'échelle la plus visible, évidente, la voie royale. Et pour la gravir, il
existe une multitude d'équipements d'escalade appropriés&amp;nbsp;: le chantage, le
harcèlement moral, le léchage de bottes, le carriérisme, la prédation, le
piston...&lt;/p&gt;
&lt;p&gt;Je dresse un tableau caricatural, mais dans chaque caricature, il y a une base
de vérité il paraît.&lt;/p&gt;
&lt;p&gt;Il existe bien évidemment, et heureusement, des moyens plus nobles et droits
d'évoluer au sein d'une entreprise. Le travail acharné, l'implication, la
loyauté, donner le meilleur de soi-même, être fiable, ne pas compter ses
heures, être agréable et aimable, facile à vivre, bon équipier.&lt;/p&gt;
&lt;p&gt;Pour faire part de mon expérience, ayant gravi plusieurs échelons, très
rapidement, j'ai aussi fait de mon mieux pour être un moteur, être force de
proposition (et de réalisation&amp;nbsp;!).&lt;/p&gt;
&lt;p&gt;Et quand on dit qu'un développeur travaille beaucoup en dehors de ses horaires
de travail, même en étant loin du clavier (sous la douche, sur la route, dans
son sommeil), c'est d'autant plus vrai quand on est investi à 100% dans sa
tâche.&lt;/p&gt;
&lt;p&gt;Mais après tout, n'est-ce pas normal&amp;nbsp;? Tout donner à l'entreprise qui nous
paie, nous fait vivre, nous permet de nous payer un toit, de quoi manger, se
payer du luxe, des vacances...&lt;/p&gt;
&lt;p&gt;Dans un sens, oui, ça paraît logique. D'un autre côté, quelqu'un a dit&amp;nbsp;: «&amp;nbsp;je
veux travailler pour vivre, et non vivre pour travailler.&amp;nbsp;»&lt;/p&gt;
&lt;p&gt;Par contre, il vaut mieux bien choisir son entreprise, et j'en parlerai dans un
&lt;a class="reference external" href="//mathieu.agopian.info/blog/taxonomie-des-entreprises.html"&gt;prochain billet&lt;/a&gt;. S'investir, être fidèle, loyal, fiable, et tout donner pour
une entreprise, qui en retour, vous aspire sans vergogne, et vous vide petit à
petit de votre substance, c'est un danger réel à éviter à tout prix&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="la-visibilite"&gt;
&lt;h2&gt;La visibilité&lt;/h2&gt;
&lt;p&gt;C'est l'autre extrême du spectre, l'autre chemin qui passe par le gain de
visibilité au sein d'une communauté, ou pour les plus chanceux et doués, au
regard de ses pairs d'une manière générale.&lt;/p&gt;
&lt;p&gt;Qui parmi vous ne connaît pas Jeff Atwood, Joel Spolsky ou Ward Cunningham,
pour ne citer qu'eux&amp;nbsp;? Ces développeurs de renom se sont fait connaître bien au
delà de la communauté de développeurs utilisant le même langage qu'eux.&lt;/p&gt;
&lt;p&gt;Sans prendre des exemples aussi extrêmes, la plupart des développeurs Python
connaissent les noms d'au moins une dizaine de personnes clé, des contributeurs
reconnus, des blogueurs influents, des créateurs de logiciels ou services qu'on
utilise au quotidien.&lt;/p&gt;
&lt;p&gt;Ces gens là sont visibles. La visibilité peut se faire à plusieurs niveaux, du
plus simple au plus difficile&amp;nbsp;:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;local&amp;nbsp;: au sein d'une entreprise, d'une équipe, d'un «&amp;nbsp;user group&amp;nbsp;»&lt;/li&gt;
&lt;li&gt;communauté&amp;nbsp;: au sein de la communauté des utilisateurs de son langage de
programmation, ou du &lt;em&gt;framework&lt;/em&gt;, &lt;em&gt;CMS&lt;/em&gt;, &lt;em&gt;CRM&lt;/em&gt; ou plus généralement outil
utilisé au quotidien&lt;/li&gt;
&lt;li&gt;global&amp;nbsp;: nationalement, voire même internationalement, être connu et reconnu
comme une référence en matière de développement&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;La visibilité s'acquiert par plusieurs moyens, pouvant être combinés&amp;nbsp;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;contributions open source&lt;/li&gt;
&lt;li&gt;création d'un outil utilisé par un grand nombre&lt;/li&gt;
&lt;li&gt;blog&lt;/li&gt;
&lt;li&gt;participation à des conférences sur son domaine d'expertise, ou mieux, en
tant qu'orateur&lt;/li&gt;
&lt;li&gt;rencontres, forums, listes de diffusion, salons IRC&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Les deux échelles permettent d'atteindre un ou plusieurs des buts listés en
début de ce billet, par des chemins différents. Nous verrons dans un prochain
billet que choisir l'une ou l'autre des échelles permet d'accéder à différents
types d'entreprises.&lt;/p&gt;
&lt;p&gt;Attention à toi, jeune développeur, ne fais pas comme moi, ne te réveille pas
«&amp;nbsp;trop tard&amp;nbsp;» en te rendant compte que tu as gravi la mauvaise échelle. Il
n'est jamais trop tard pour recommencer à gravir «&amp;nbsp;la bonne&amp;nbsp;», mais ça implique
de repartir d'en bas&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;Certains me diront qu'il est tout à fait possible de gravir les deux en même
temps. Je le crois, en effet, mais je doute que ce soit à la portée de tout le
monde. Et de la même manière qu'on sait qu'il est impossible pour un cerveau de
faire du multi-tâche, je pense qu'essayer de gravir les deux échelles sera non
seulement épuisant et difficilement faisable sur le long terme, mais surtout,
une échelle sera toujours privilégiée par rapport à l'autre.&lt;/p&gt;
&lt;p&gt;D'autres me diront que gravir une échelle fait «&amp;nbsp;gravir des échelons&amp;nbsp;»
automatiquement sur l'autre. C'est aussi vrai, mais dans une moindre mesure.
Encore une fois, utiliser une échelle se fera généralement au détriment de
l'autre.&lt;/p&gt;
&lt;p&gt;Le but de ce billet est surtout de faire partager ma prise de conscience
tardive. Tant qu'on est jeune, fougueux et plein d'énergie, il vaut mieux se
concentrer sur une seule échelle, et le faire consciemment, au lieu de se
lancer tête baissée et de potentiellement le regretter plus tard&amp;nbsp;!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="edit-19h29"&gt;
&lt;h2&gt;Edit&amp;nbsp;: 19h29&lt;/h2&gt;
&lt;p&gt;Suite à la &lt;a class="reference external" href="https://larlet.fr/david/thoughts/#sharing"&gt;réponse de David Larlet&lt;/a&gt;, j'ai repensé à ce billet, et ma première
réaction a été «&amp;nbsp;mais il m'énerve ce David à avoir raison, et l'écrire si
bien&amp;nbsp;!&amp;nbsp;».&lt;/p&gt;
&lt;p&gt;Si vous n'êtes pas encore abonnés à son flux RSS, je ne peut que vous le
conseiller, ses billets sont source d'inspiration, très bien écrits,
synthétiques et concis à souhait.&lt;/p&gt;
&lt;p&gt;Revenons à nos moutons&amp;nbsp;: oui, je pense que le conseil de David de &lt;strong&gt;partager&lt;/strong&gt;
est le meilleur qu'on puisse suivre, mais je pense aussi que c'est orthogonal
à la pensée initiale de ce billet. Je pense effectivement que la meilleure
chose à faire, &lt;strong&gt;quelle que soit l'échelle qu'on choisi&lt;/strong&gt;, c'est de partager,
et ce pour toutes les raisons énoncées par David.&lt;/p&gt;
&lt;p&gt;Ainsi, sur l'échelle de l'entreprise, on partagera par le biais de formations
internes, de discussions à bâtons rompus à la pause café, de conseils donnés ou
reçu lors de demande d'aide ou de conseil, de la revue de code.&lt;/p&gt;
&lt;p&gt;Sur l'échelle de la visibilité, on partagera en conférences, par des
contributions open source, etc.&lt;/p&gt;
&lt;p&gt;Par contre, je reste convaincu qu'il existe vraiment deux échelles, chacune
menant à une position différente, plus ou moins avantageuse selon le type
d'entreprise qu'on vise. Et c'est ce que j'espère montrer et expliquer plus
clairement avec le &lt;a class="reference external" href="//mathieu.agopian.info/blog/taxonomie-des-entreprises.html"&gt;prochain billet&lt;/a&gt; sur la taxonomie des entreprises.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category></entry><entry><title>Automatiser son flake8 avec vim et syntastic</title><link href="//mathieu.agopian.info/blog/automatiser-son-flake8-avec-vim-et-syntastic.html" rel="alternate"></link><published>2012-09-30T11:40:00+02:00</published><updated>2012-09-30T11:40:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2012-09-30:/blog/automatiser-son-flake8-avec-vim-et-syntastic.html</id><summary type="html">&lt;p&gt;Toi, ami développeur Python, j'espère que tu connais déjà &lt;a class="reference external" href="https://bitbucket.org/tarek/flake8"&gt;flake8&lt;/a&gt; &lt;a class="footnote-reference" href="#id1" id="id2"&gt;[1]&lt;/a&gt;. Flake8 te
permet de valider en une seule fois que ton code Python est propre au regard de
&lt;a class="reference external" href="https://github.com/jcrocholl/pep8/"&gt;pep8&lt;/a&gt; &lt;a class="footnote-reference" href="#id3" id="id4"&gt;[2]&lt;/a&gt; et de &lt;a class="reference external" href="https://launchpad.net/pyflakes"&gt;pyflakes&lt;/a&gt; &lt;a class="footnote-reference" href="#id5" id="id6"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Si tu ne connais aucun des trois programmes que je viens de nommer, et que …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Toi, ami développeur Python, j'espère que tu connais déjà &lt;a class="reference external" href="https://bitbucket.org/tarek/flake8"&gt;flake8&lt;/a&gt; &lt;a class="footnote-reference" href="#id1" id="id2"&gt;[1]&lt;/a&gt;. Flake8 te
permet de valider en une seule fois que ton code Python est propre au regard de
&lt;a class="reference external" href="https://github.com/jcrocholl/pep8/"&gt;pep8&lt;/a&gt; &lt;a class="footnote-reference" href="#id3" id="id4"&gt;[2]&lt;/a&gt; et de &lt;a class="reference external" href="https://launchpad.net/pyflakes"&gt;pyflakes&lt;/a&gt; &lt;a class="footnote-reference" href="#id5" id="id6"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Si tu ne connais aucun des trois programmes que je viens de nommer, et que tu
n'utilise pas non plus &lt;a class="reference external" href="http://www.logilab.org/project/pylint"&gt;pylint&lt;/a&gt; &lt;a class="footnote-reference" href="#id7" id="id8"&gt;[4]&lt;/a&gt;, je pense qu'il est temps pour toi d'arrêter de
lire cet article, et de te documenter. Au plus vite. Non, en fait, maintenant,
là, tout de suite.&lt;/p&gt;
&lt;p&gt;Pour les têtes dures, ces utilitaires permettent de valider que le code est
compatible avec la &lt;a class="reference external" href="http://www.python.org/dev/peps/pep-0008/"&gt;pep 0008&lt;/a&gt; &lt;a class="footnote-reference" href="#id9" id="id10"&gt;[5]&lt;/a&gt;, qu'il n'y a pas d'erreur de syntaxe, d'import
ou de variable inutilisés, etc...&lt;/p&gt;
&lt;p&gt;Ils permettent donc d'être sûrs de la qualité de la mise en forme du code (et
malheureusement pas de la qualité du code lui-même). Si quelqu'un connaît un
outil qui permette de faire ça, je suis preneur&amp;nbsp;: il y a bien &lt;a class="reference external" href="https://fr.wikipedia.org/wiki/Nombre_cyclomatique"&gt;l'indice de
complexité de McCabe&lt;/a&gt; &lt;a class="footnote-reference" href="#id11" id="id12"&gt;[6]&lt;/a&gt; (qui peut être fournie par &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt;), mais il ne
permet pas de mesurer la qualité totale d'un code. J'imagine que c'est trop
subjectif pour être mesuré objectivement.&lt;/p&gt;
&lt;div class="section" id="je-suis-trop-faineant"&gt;
&lt;h2&gt;Je suis trop fainéant&lt;/h2&gt;
&lt;p&gt;C'est très bien, d'être fainéant. Être fainéant, c'est vouloir se simplifier la
vie, en obtenir plus en travaillant moins, c'est un &lt;a class="reference external" href="http://agopian.info/djangocong/dplf.html"&gt;noble but&lt;/a&gt; &lt;a class="footnote-reference" href="#id13" id="id14"&gt;[7]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et je dois avouer que lancer à la main &lt;tt class="docutils literal"&gt;pep8&lt;/tt&gt; puis &lt;tt class="docutils literal"&gt;pyflakes&lt;/tt&gt; (j'utilisais
en fait déjà un &lt;a class="reference external" href="http://www.vim.org/scripts/script.php?script_id=2441"&gt;plugin Vim pour pyflakes&lt;/a&gt; &lt;a class="footnote-reference" href="#id15" id="id16"&gt;[8]&lt;/a&gt;) régulièrement, puis retourner sur
le code qu'on pensait avoir fini pour le nettoyer, retrouver la ligne
correspondante à chaque erreur, c'était fastidieux.&lt;/p&gt;
&lt;p&gt;Flake8 permet de récupérer les erreurs de &lt;tt class="docutils literal"&gt;pep8&lt;/tt&gt; et de &lt;tt class="docutils literal"&gt;pyflakes&lt;/tt&gt; en une
seule fois, ce qui est déjà une amélioration. Mais ce n'est pas suffisant, il
reste toujours à le lancer manuellement...&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="syntastic-a-notre-secours"&gt;
&lt;h2&gt;Syntastic à notre secours&lt;/h2&gt;
&lt;p&gt;C'est là qu'intervient notre sauveur&amp;nbsp;: &lt;a class="reference external" href="https://github.com/scrooloose/syntastic"&gt;syntastic&lt;/a&gt; &lt;a class="footnote-reference" href="#id17" id="id18"&gt;[9]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Il s'installe en un tournemain avec &lt;a class="reference external" href="https://github.com/tpope/vim-pathogen"&gt;pathogen&lt;/a&gt; &lt;a class="footnote-reference" href="#id19" id="id20"&gt;[10]&lt;/a&gt;, que vous devriez utiliser (non,
ce n'est pas un conseil, c'est un ordre).&lt;/p&gt;
&lt;p&gt;La doc d'installation de &lt;tt class="docutils literal"&gt;syntastic&lt;/tt&gt; est limpide, et si vous avez &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt;
installé, vous n'avez rien d'autre à faire&amp;nbsp;! En fait, &lt;tt class="docutils literal"&gt;syntastic&lt;/tt&gt; va
utiliser le premier exécutable qu'il trouve parmi &lt;tt class="docutils literal"&gt;flake8&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;pyflakes&lt;/tt&gt; et
&lt;tt class="docutils literal"&gt;pylint&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Une fois &lt;tt class="docutils literal"&gt;syntastic&lt;/tt&gt; installé, à chaque sauvegarde de votre fichier, vous
verrez une marque apparaître dans la marge de gauche pour chaque violation de
règle de formatage.&lt;/p&gt;
&lt;p&gt;Il n'y a donc plus aucune raison pour ne pas valider ton code&amp;nbsp;! Fais toi une
faveur, ainsi qu'à ceux qui devront relire ta prose.&lt;/p&gt;
&lt;hr class="docutils" /&gt;
&lt;table class="docutils footnote" frame="void" id="id1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id2"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://bitbucket.org/tarek/flake8"&gt;https://bitbucket.org/tarek/flake8&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id4"&gt;[2]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://github.com/jcrocholl/pep8/"&gt;https://github.com/jcrocholl/pep8/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id6"&gt;[3]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://launchpad.net/pyflakes"&gt;https://launchpad.net/pyflakes&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id7" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id8"&gt;[4]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://www.logilab.org/project/pylint"&gt;http://www.logilab.org/project/pylint&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id9" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id10"&gt;[5]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://www.python.org/dev/peps/pep-0008/"&gt;http://www.python.org/dev/peps/pep-0008/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id11" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id12"&gt;[6]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://fr.wikipedia.org/wiki/Nombre_cyclomatique"&gt;https://fr.wikipedia.org/wiki/Nombre_cyclomatique&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id13" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id14"&gt;[7]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://agopian.info/djangocong/dplf.html"&gt;http://agopian.info/djangocong/dplf.html&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id15" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id16"&gt;[8]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://www.vim.org/scripts/script.php?script_id=2441"&gt;http://www.vim.org/scripts/script.php?script_id=2441&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id17" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id18"&gt;[9]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://github.com/scrooloose/syntastic"&gt;https://github.com/scrooloose/syntastic&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="id19" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#id20"&gt;[10]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://github.com/tpope/vim-pathogen"&gt;https://github.com/tpope/vim-pathogen&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="django"></category></entry><entry><title>Vim + Screen : le pair-prog des champions !</title><link href="//mathieu.agopian.info/blog/vim-screen-le-pair-prog-des-champions.html" rel="alternate"></link><published>2012-09-26T08:43:00+02:00</published><updated>2012-09-26T08:43:00+02:00</updated><author><name>Mathieu Agopian</name></author><id>tag:mathieu.agopian.info,2012-09-26:/blog/vim-screen-le-pair-prog-des-champions.html</id><summary type="html">&lt;p&gt;Le &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Programmation_en_bin%C3%B4me"&gt;pair-programming&lt;/a&gt; &lt;a class="footnote-reference" href="#id1" id="id2"&gt;[1]&lt;/a&gt; fait partie des bonnes pratiques du développement, ce
n'est plus à prouver&amp;nbsp;: il permet de produire un code de meilleure qualité,
mieux pensé et construit.&lt;/p&gt;
&lt;p&gt;On entend souvent dire qu'il est (deux fois) moins productif, vu qu'on est deux
à produire un seul morceau de code, mais …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Le &lt;a class="reference external" href="http://fr.wikipedia.org/wiki/Programmation_en_bin%C3%B4me"&gt;pair-programming&lt;/a&gt; &lt;a class="footnote-reference" href="#id1" id="id2"&gt;[1]&lt;/a&gt; fait partie des bonnes pratiques du développement, ce
n'est plus à prouver&amp;nbsp;: il permet de produire un code de meilleure qualité,
mieux pensé et construit.&lt;/p&gt;
&lt;p&gt;On entend souvent dire qu'il est (deux fois) moins productif, vu qu'on est deux
à produire un seul morceau de code, mais dans la pratique, il permet au
contraire d'éviter des pièges ou bugs qui auraient pris un temps fou à
résoudre.&lt;/p&gt;
&lt;p&gt;Un autre avantage est de pouvoir mettre le pied à l'étrier d'un autre
développeur sur son code, tout en douceur et efficacement. Ou encore de
partager la paternité, la connaissance et l'expertise sur une base de code,
entre plusieurs développeurs.&lt;/p&gt;
&lt;p&gt;Et d'une manière générale, c'est tellement plus sympathique et agréable de
pouvoir réfléchir à deux, discuter, échanger, se conseiller mutuellement&amp;nbsp;!&lt;/p&gt;
&lt;p&gt;Bref, comme je le disais, les mérites du pair-programming ne sont plus à
prouver.&lt;/p&gt;
&lt;p&gt;Mais comment en profiter quand on travaille à distance&amp;nbsp;? Je pense par
exemple aux personnes en télé-travail, ou qui veulent participer à des sprints
à distance.&lt;/p&gt;
&lt;p&gt;Tu l'as deviné (facile, c'est dans le titre), grâce à VIM (ou tout autre
éditeur en ligne de commande, comme par exemple l'autre dont on ne saurait
prononcer le nom), le logiciel &lt;a class="reference external" href="http://www.gnu.org/s/screen/"&gt;screen&lt;/a&gt; &lt;a class="footnote-reference" href="#id3" id="id4"&gt;[2]&lt;/a&gt;, tout ça sur SSH.&lt;/p&gt;
&lt;p&gt;Je te fais ici un résumé de ce qui a été très bien expliqué dans un &lt;a class="reference external" href="http://blog.siyelo.com/remote-pair-programming-with-screen"&gt;article
du blog de Siyelo&lt;/a&gt; &lt;a class="footnote-reference" href="#id5" id="id6"&gt;[3]&lt;/a&gt;, adapté, simplifié et automatisé à ma sauce.&lt;/p&gt;
&lt;div class="section" id="installer-un-serveur-ssh"&gt;
&lt;h2&gt;Installer un serveur SSH&lt;/h2&gt;
&lt;p&gt;Pour ça, tu es bien gentil, mais je pense que tu vas y arriver tout seul comme
un grand.&lt;/p&gt;
&lt;p&gt;Si nécessaire, pour que ton collègue distant puisse se connecter en local,
rajoute donc une règle de redirection sur ton routeur/ta box&amp;nbsp;, par exemple du
port externe &lt;tt class="docutils literal"&gt;22222&lt;/tt&gt;, en &lt;tt class="docutils literal"&gt;TCP&lt;/tt&gt;, vers le port &lt;tt class="docutils literal"&gt;22&lt;/tt&gt; de l'adresse IP de ton
ordinateur.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="first admonition-title"&gt;Note&lt;/p&gt;
&lt;p class="last"&gt;Il existe un logiciel très pratique pour gérer ses règles de
redirection sans avoir à se connecter à l'interface de sa box&amp;nbsp;:
&lt;a class="reference external" href="http://upnp-portmapper.sourceforge.net/"&gt;portmapper&lt;/a&gt; &lt;a class="footnote-reference" href="#id7" id="id8"&gt;[4]&lt;/a&gt; (&lt;a class="reference external" href="http://korben.info/comment-ouvrir-et-mapper-facilement-des-ports-sur-votre-routeur.html"&gt;Korben&lt;/a&gt; &lt;a class="footnote-reference" href="#id9" id="id10"&gt;[5]&lt;/a&gt; en parle d'ailleurs très bien).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="installer-screen"&gt;
&lt;h2&gt;Installer screen&lt;/h2&gt;
&lt;p&gt;Là encore, rien de bien compliqué.&lt;/p&gt;
&lt;p&gt;&lt;tt class="docutils literal"&gt;screen&lt;/tt&gt; permet de multiplexer plusieurs &lt;tt class="docutils literal"&gt;shells&lt;/tt&gt;. C'est aussi lui qui va
rendre possible de se connecter à plusieurs sur un seul et même &lt;tt class="docutils literal"&gt;shell&lt;/tt&gt;,
celui par exemple qui lancera l'éditeur. Cela permet donc dans la pratique de
pouvoir éditer un fichier à plusieurs, et de voir instantanément toutes les
modifications.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configurer"&gt;
&lt;h2&gt;Configurer&lt;/h2&gt;
&lt;p&gt;L'idée est simple&amp;nbsp;: un utilisateur local (que l'on nommera &lt;tt class="docutils literal"&gt;pair&lt;/tt&gt;) va lancer
le &lt;tt class="docutils literal"&gt;screen&lt;/tt&gt;, sur lequel tu pourra te connecter. Il suffira ensuite de donner
le mot de passe de ce compte à ton collègue distant, pour qu'il s'y connecte en
ssh, puis se connecte lui aussi à ce &lt;tt class="docutils literal"&gt;screen&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Cet utilisateur &lt;tt class="docutils literal"&gt;pair&lt;/tt&gt; sera donc utilisé pour programmer, voire même
&lt;em&gt;commiter&lt;/em&gt; le code, avec éventuellement un &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.gitconfig&lt;/span&gt;&lt;/tt&gt; ou &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.hgrc&lt;/span&gt;&lt;/tt&gt; avec
un nom évocateur du genre &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;pair-benoit-mathieu&lt;/span&gt;&lt;/tt&gt;, afin que les &lt;em&gt;commits&lt;/em&gt;
puissent être directement attribués aux bonnes personnes.&lt;/p&gt;
&lt;p&gt;Pour créer ce nouvel utilisateur&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo adduser pair
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Enfin, toute la configuration de &lt;tt class="docutils literal"&gt;screen&lt;/tt&gt; est mise dans un fichier
&lt;tt class="docutils literal"&gt;.screenrc&lt;/tt&gt; à la racine du compte&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
hardstatus on
hardstatus alwayslastline
startup_message off
termcapinfo xterm ti&amp;#64;:te&amp;#64;
hardstatus string &amp;quot;%{= kG}%-w%{.rW}%n %t%{-}%+w %=%{..G} %H %{..Y} %m/%d %C%a &amp;quot;
term screen-256color

multiuser on
acladd &amp;lt;ton user&amp;gt;  # user allowed to connect
&lt;/pre&gt;
&lt;p&gt;Prends bien soin de remplacer &lt;tt class="docutils literal"&gt;&amp;lt;ton user&amp;gt;&lt;/tt&gt; par ton utilisateur, afin que
lorsque le &lt;tt class="docutils literal"&gt;screen&lt;/tt&gt; soit lancé, tu puisse t'y connecter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="utiliser"&gt;
&lt;h2&gt;Utiliser&lt;/h2&gt;
&lt;p&gt;Lance un &lt;tt class="docutils literal"&gt;screen&lt;/tt&gt; sur l'utilisateur &lt;tt class="docutils literal"&gt;pair&lt;/tt&gt; en mode détaché&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo -u pair screen -d -m -S pairprog  &lt;span class="c1"&gt;# -S nomme la session&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Connecte toi au &lt;tt class="docutils literal"&gt;screen&lt;/tt&gt; de cet utilisateur&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;screen -x pair/pairprog
&lt;span class="c1"&gt;# une fois connecté au screen&lt;/span&gt;
vim
&lt;span class="c1"&gt;# créer une nouvelle fenêtre dans le screen : &amp;lt;ctrl-a c&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# passer à la fenêtre suivante : &amp;lt;ctrl-a n&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# passer à la fenêtre précédente : &amp;lt;ctrl-a p&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# passer à la fenêtre 1 : &amp;lt;ctrl-a 1&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# détruire la fenêtre courante : &amp;lt;ctrl-a k&amp;gt; ou &amp;lt;ctrl-d&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Si tu es un vrai bon fainéant comme moi, tu aura bien sûr sauté sur l'occasion
d'en faire un alias dans ton &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.bashrc&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Indique l'utilisateur &lt;tt class="docutils literal"&gt;pair&lt;/tt&gt; et son mot de passe, ton adresse IP et le port
de connexion SSH à ton collègue pour qu'il puisse te rejoindre&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ssh pair@&amp;lt;ip de ta box&amp;gt; -p &lt;span class="m"&gt;22222&lt;/span&gt;
screen -x pairprog  &lt;span class="c1"&gt;# alias pair=&amp;#39;screen -x pairprog&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Petite astuce de sioux, pour que l'utilisateur distant n'ai même pas besoin de
se connecter manuellement au &lt;tt class="docutils literal"&gt;screen&lt;/tt&gt; (ni même à lancer l'alias), à rajouter
au &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;~/.bashrc&lt;/span&gt;&lt;/tt&gt; de l'utilisateur &lt;tt class="docutils literal"&gt;pair&lt;/tt&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TERM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;xterm-256color  &lt;span class="c1"&gt;# compatibility with screen&lt;/span&gt;

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

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

class MyHttpResponseServerError(http.HttpResponse):
    status_code = 503

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

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

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

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

...

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

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

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

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

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

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

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

import floppyforms as forms


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

import floppyforms as forms
from form_utils.forms import BetterForm


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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

[mysqldump]
default-character-set=utf8

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

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

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

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

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

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

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


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

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

DocumentRoot /path/to/django/project

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

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

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

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

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

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

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

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