<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>http://gillesfabio.com/</id>
  <title>Gilles Fabio</title>
  <subtitle>Gilles Fabio's Personal Website</subtitle>
  <link href="http://gillesfabio.com/atom.xml" rel="self" type="application/atom+xml" />
  <link href="http://gillesfabio.com/" rel="alternate" type="text/html" />
  <updated>2015-02-17T08:56:41.598Z</updated>
  <author>
    <name>Gilles Fabio</name>
  </author>
  <rights>© 2015 Gilles Fabio</rights>
  
  <entry xml:lang="en">
    <id>http://gillesfabio.com/blog/2011/03/01/rvm-for-pythonistas-virtualenv-for-rubyists</id>
    <link href="http://gillesfabio.com/blog/2011/03/01/rvm-for-pythonistas-virtualenv-for-rubyists" rel="alternate" type="text/html" />
    <title>RVM for Pythonistas, virtualenv for Rubyists</title>
    <published>2011-03-01T00:00:00.000Z</published>
    <updated>2011-03-01T00:00:00.000Z</updated>
    <author>
      <name>Gilles Fabio</name>
    </author>
    <content type="html">
      &lt;p&gt;You are or have been a Pythonista? You are or were in love with
&lt;a href=&quot;http://pypi.python.org/pypi/virtualenv&quot;&gt;virtualenv&lt;/a&gt;,
&lt;a href=&quot;http://pypi.python.org/pypi/virtualenvwrapper&quot;&gt;virtualenwrapper&lt;/a&gt; or
&lt;a href=&quot;http://www.buildout.org/&quot;&gt;Buildout&lt;/a&gt;? Now you do some Ruby you are looking for
the same wonderful tools? You should take a look at &lt;a href=&quot;http://rvm.beginrescueend.com/&quot;&gt;RVM&lt;/a&gt;
or Ruby Version Manager.&lt;/p&gt;
&lt;p&gt;Oh, well. You are or have been a Rubyist? You are or were in love with &lt;a href=&quot;http://rvm.beginrescueend.com/&quot;&gt;RVM&lt;/a&gt;?
Now you do some Python you are looking for the same wonderful tool? You should
take a look at &lt;a href=&quot;http://pypi.python.org/pypi/virtualenv&quot;&gt;virtualenv&lt;/a&gt; (with
&lt;a href=&quot;http://pypi.python.org/pypi/virtualenvwrapper&quot;&gt;virtualenvwrapper&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;
&lt;h3 id=&quot;i-am-a-pythonista&quot;&gt;I am a Pythonista&lt;/h3&gt;
&lt;p&gt;First, make sure &lt;a href=&quot;http://pypi.python.org/pypi/pip&quot;&gt;pip&lt;/a&gt; is installed on your system.&lt;/p&gt;
&lt;p&gt;Then, in your terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pip install virtualenvwrapper
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Create the directory which will contain your virtual environments. For example,
&lt;code&gt;$HOME/.virtualenvs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir ~/.virtualenvs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit your &lt;code&gt;$HOME/bash_profile&lt;/code&gt; file and add these lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export WORKON_HOME=$HOME/.virtualenvs
export PIP_VIRTUALENV_BASE=$WORKON_HOME
export PIP_RESPECT_VIRTUALENV=true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Source it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Troubleshooting? Check the &lt;a href=&quot;http://www.doughellmann.com/docs/virtualenvwrapper/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;i-am-a-rubyist&quot;&gt;I am a Rubyist&lt;/h3&gt;
&lt;p&gt;First, make sure &lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; is installed on your system.&lt;/p&gt;
&lt;p&gt;Then, in your terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash &lt; &lt;( curl http://rvm.beginrescueend.com/releases/rvm-install-head )
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit your &lt;code&gt;$HOME/.bash_profile&lt;/code&gt; file and add this line at the very end:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;[[ -s &quot;$HOME/.rvm/scripts/rvm&quot; ]] &amp;&amp; . &quot;$HOME/.rvm/scripts/rvm&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Source it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source ~/.rvm/scripts/rvm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Troubleshooting? Check the &lt;a href=&quot;http://rvm.beginrescueend.com/rvm/install/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;managing-multiple-interpreters&quot;&gt;Managing multiple interpreters&lt;/h2&gt;
&lt;h3 id=&quot;i-am-a-pythonista&quot;&gt;I am a Pythonista&lt;/h3&gt;
&lt;p&gt;You have to install the different Python versions on your system (via your
package manager). When you will create a virtual environment with
virtualenv, you will pass the interpreter path of the desired Python
version as &lt;code&gt;-p&lt;/code&gt; argument of &lt;code&gt;mkvirtualenv&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkvirtualenv -p /opt/local/bin/python3 myproject
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;i-am-a-rubyist&quot;&gt;I am a Rubyist&lt;/h3&gt;
&lt;p&gt;RVM compiles and installs the desired Ruby interpreter and Ruby version in
the current user directory. It supports MRI/YARV, Rubinius, JRuby, Ruby
Enterprise Edition, MagLev, IronRuby, MacRuby and GoRuby. This can be
achieved in a single one command.&lt;/p&gt;
&lt;p&gt;To list the available interpreters and versions to which it may install:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm list known
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let’s install the original implementation in version 1.8.7 and 1.9.2
(make sure &lt;a href=&quot;http://subversion.tigris.org/&quot;&gt;Subversion&lt;/a&gt; is installed):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm install 1.8.7
rvm install 1.9.2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This takes some compilation time and you’ve done.&lt;/p&gt;
&lt;p&gt;To list the installed Ruby versions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm list rubies
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To switch between interpreters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use RUBY_VERSION
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example, switching to 1.8.2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use 1.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or switching to 1.9.2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use 1.9.2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To set up the default interpreter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm --default use RUBY_VERSION
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example, if you want to use Ruby 1.9.2 as default interpreter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm --default use 1.9.2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To switch back to your default system interpreter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use system
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is very powerful. Check the &lt;a href=&quot;http://rvm.beginrescueend.com/rubies/default/&quot;&gt;documentation&lt;/a&gt;
to know more about available commands and options.&lt;/p&gt;
&lt;h2 id=&quot;managing-multiple-environments&quot;&gt;Managing multiple environments&lt;/h2&gt;
&lt;h3 id=&quot;i-am-a-pythonista&quot;&gt;I am a Pythonista&lt;/h3&gt;
&lt;p&gt;Generally, with virtualenv, you create one environment per project and/or
project stage (development, testing, staging, production, etc). This can be
accomplished with the &lt;code&gt;mkvirtualenv&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkvirtualenv --no-site-packages PROJECT_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example, if my project name is “superdjango”:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkvirtualenv --no-site-packages superdjango
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;--no-site-packages&lt;/code&gt; option removes the standard &lt;code&gt;site-packages&lt;/code&gt; directory
from the environment &lt;code&gt;sys.path&lt;/code&gt;. I recommend to use this option if you want
more isolation. It avoids dealing with system packages conflicts.&lt;/p&gt;
&lt;p&gt;As seen above, you can specify the Python version with the &lt;code&gt;-p&lt;/code&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkvirtualenv --no-site-packages -p /opt/local/bin/python3 PROJECT_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you added &lt;code&gt;export PIP_RESPECT_VIRTUALENV=true&lt;/code&gt; in your &lt;code&gt;$HOME/.bash_profile&lt;/code&gt;
file, when your environment is active, &lt;code&gt;pip&lt;/code&gt; auto-installs packages in this
environment. You do not have to prefix it with any &lt;code&gt;sudo&lt;/code&gt; command or have to
pass it any option. Just this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install PACKAGE
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install Django
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Otherwise, you need to use the &lt;code&gt;-E&lt;/code&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install -E ENVIRONMENT_NAME PACKAGE
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install -E superdjango Django
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To list all available environments, use the &lt;code&gt;workon&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;workon
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To activate an environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;workon ENVIRONMENT_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;workon superdjango
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;The first time you create a virtual environment with the &lt;code&gt;mkvirtualenv&lt;/code&gt;
command, you will auto-switch to this environment instantly (the environment
name prefixes your session prompt).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To deactivate the current environment, use the &lt;code&gt;deactivate&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deactivate
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To delete a virtual environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rmvirtualenv ENVIRONMENT_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rmvirtualenv superdjango
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;i-am-a-rubyist&quot;&gt;I am a Rubyist&lt;/h3&gt;
&lt;p&gt;Where virtualenv has “environments”, RVM has “gemsets”. Interpreters and
packages are all separated and self-contained from system and from each other.
Creating a virtual environment with virtualenv is creating a gemset with RVM.
Generally, you create one gemset per project and/or project stage (development,
testing, staging, production, etc). This can be done with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use RUBY_VERSION@GEMSET_NAME --create
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use 1.9.2@myproject --create
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is equivalent to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use 1.9.2
rvm gemset create myproject
rvm gemset use myproject
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When your gemset is active, using &lt;code&gt;gem install&lt;/code&gt; command will install packages
directly in your gemset. So you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem install PACKAGE
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will install the given package in your current active gemset directory.&lt;/p&gt;
&lt;p&gt;To know the path of this directory, you can use the &lt;code&gt;gemdir&lt;/code&gt; RVM command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm gemdir
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Deleting a gemset is simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm gemset delete GEMSET_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm gemset delete myproject
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To empty a gemset (removing all installed gems):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm gemset empty GEMSET_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm gemset empty myproject
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;managing-project-dependencies&quot;&gt;Managing project dependencies&lt;/h2&gt;
&lt;h3 id=&quot;i-am-a-pythonista&quot;&gt;I am a Pythonista&lt;/h3&gt;
&lt;p&gt;Managing project dependencies with Pip and virtualenv is crazy simple. You
just have to create a text file containing package names (and optionally
package versions) and give this file to Pip via the &lt;code&gt;-r&lt;/code&gt; option. Everything
is clearly well explained in the &lt;a href=&quot;http://pip.openplans.org/requirement-format.html&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To give you an example, for my “superdjango” project, I create a
&lt;code&gt;requirements.txt&lt;/code&gt; file to start my project with &lt;a href=&quot;http://djangoproject.com&quot;&gt;Django&lt;/a&gt;
1.2.x and &lt;a href=&quot;http://south.aeracode.org/&quot;&gt;South&lt;/a&gt; support. This &lt;code&gt;requirements.txt&lt;/code&gt;
file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Django &gt;= 1.2
South == 0.7.2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If I don’t already created my environment, I create it right now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkvirtualenv --no-site-packages superdjango
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And I install project dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install -r /path/to/my/requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will install the latest Django 1.2 version and South 0.7.2 into my
virtual environment.&lt;/p&gt;
&lt;h3 id=&quot;i-am-a-rubyist&quot;&gt;I am a Rubyist&lt;/h3&gt;
&lt;p&gt;Managing project dependencies with RVM, &lt;a href=&quot;http://rubygems.org/&quot;&gt;RubyGem&lt;/a&gt; and
&lt;a href=&quot;http://gembundler.com/&quot;&gt;Bundler&lt;/a&gt; is crazy simple too.&lt;/p&gt;
&lt;p&gt;Where Pip has “requirements” files, Bundler has “Gemfile” files.&lt;/p&gt;
&lt;p&gt;First, you need to create a dedicated gemset for the project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use RUBY_VERSION@GEMSET_NAME --create
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use 1.9.2@myproject --create
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the same of these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rvm use 1.9.2
rvm gemset create myproject
rvm gemset use myproject
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I install Bundler into my gemset:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem install bundler
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I create a directory for my project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /path/to/my/workspace
mkdir myproject
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I go in the directory and create an empty &lt;code&gt;Gemfile&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd myproject
bundle init
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then I edit the &lt;code&gt;Gemfile&lt;/code&gt; file and add my dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-ruby&quot;&gt;source &quot;http://rubygems.org&quot;

gem &quot;sinatra&quot;, &quot;~&gt; 0.9.0&quot;
gem &quot;rack-cache&quot;
gem &quot;rack-bug&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I install dependencies in the current gemset:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle install
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s all. Everything is clearly well explained in the &lt;a href=&quot;http://gembundler.com/rationale.html&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;RVM and virtualenv (coupled with virtualenvwrapper) are both powerful tools
which considerably improve your productivity and reduce headache fighting with
dependencies. Today, I can’t even imagine living without. I didn’t cover
Buildout, the Python alternative to virtualenv, because it works a different
way but it’s a wonderful tool you should give it a try. These articles,
written by &lt;a href=&quot;http://jacobian.org&quot;&gt;Jacob Kaplan-Moss&lt;/a&gt;, are must-read:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://jacobian.org/writing/django-apps-with-buildout/&quot;&gt;Developing Django apps with zc.buildout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://jacobian.org/writing/more-buildout-notes/&quot;&gt;More buildout notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

    </content>
    
    <category term="python" />
    
    <category term="ruby" />
    
    <category term="development" />
    
  </entry>
  
  <entry xml:lang="en">
    <id>http://gillesfabio.com/blog/2010/12/17/installing-symfony2-on-mac-os-x</id>
    <link href="http://gillesfabio.com/blog/2010/12/17/installing-symfony2-on-mac-os-x" rel="alternate" type="text/html" />
    <title>Installing Symfony2 on Mac OS X</title>
    <published>2010-12-17T00:00:00.000Z</published>
    <updated>2010-12-17T00:00:00.000Z</updated>
    <author>
      <name>Gilles Fabio</name>
    </author>
    <content type="html">
      &lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://symfony-reloaded.org&quot;&gt;Symfony2&lt;/a&gt; requires at least PHP 5.3.2. If your
Mac is not ready, you can follow these &lt;a href=&quot;http://gillesfabio.com/blog/2010/12/17/getting-php-5-3-on-mac-os-x/&quot;&gt;instructions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And make sure &lt;a href=&quot;http://git-scm.com/&quot;&gt;Git&lt;/a&gt; is installed on your system.&lt;/p&gt;
&lt;h2 id=&quot;sandbox-and-generators&quot;&gt;Sandbox and generators&lt;/h2&gt;
&lt;p&gt;There are three ways to get the initial structure of a new Symfony2 project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symfony/symfony-sandbox&quot;&gt;symfony-sandbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symfony/symfony-bootstrapper&quot;&gt;symfony-bootstrapper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Garfield-fr/Symfony2Project&quot;&gt;Symfony2Project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Currently, the recommended way is the Sandbox (Symfony Bootstrapper is still
in development, undocumented and can be buggy).&lt;/p&gt;
&lt;p&gt;Symfony2Project is an alternative created by Bertrand Zuchuat. You should give
it a try.&lt;/p&gt;
&lt;h2 id=&quot;creating-a-new-project-with-the-sandbox&quot;&gt;Creating a new project with the Sandbox&lt;/h2&gt;
&lt;p&gt;To get the Sandbox, go in your development workspace and grab the source:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /path/to/your/workspace
git clone https://github.com/symfony/symfony-sandbox.git
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example, my development workspace is &lt;code&gt;$HOME/Code/PHP&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd $HOME/Code/PHP
git clone https://github.com/symfony/symfony-sandbox.git
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Create a copy of the &lt;code&gt;symfony-sandbox&lt;/code&gt; folder with the name of your project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cp -r symfony-sandbox project
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example, I name my project &lt;code&gt;sf2project&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cp -r symfony-sandbox sf2project
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Fix some permissions for the &lt;code&gt;cache&lt;/code&gt; and &lt;code&gt;logs&lt;/code&gt; folders:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd project
chmod -R 777 app/cache
chmod -R 777 app/logs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With my &lt;code&gt;sf2project&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd sf2project
chmod -R 777 app/cache
chmod -R 777 app/logs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Create a VirtualHost (&lt;a href=&quot;http://gillesfabio.com/blog/2010/12/17/getting-php-5-3-on-mac-os-x/&quot;&gt;instructions&lt;/a&gt;)
with a similar configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-apache&quot;&gt;NameVirtualHost *:80

&lt;VirtualHost *:80&gt;
    DocumentRoot &quot;/absolute/path/to/your/project/web&quot;
    ServerName project.localhost
    &lt;Directory &quot;/absolute/path/to/your/project/web&quot;&gt;
        Options Indexes FollowSymlinks
        AllowOverride All
        Order allow,deny
        Allow from all
    &lt;/Directory&gt;
    ErrorLog &quot;logs/project-error_log&quot;
    CustomLog &quot;logs/project-access_log&quot; common
&lt;/VirtualHost&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example of my &lt;code&gt;sf2project&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-apache&quot;&gt;NameVirtualHost *:80

&lt;VirtualHost *:80&gt;
    DocumentRoot &quot;/Users/gilles/Code/PHP/sf2project/web&quot;
    ServerName sf2project.localhost
    &lt;Directory &quot;/Users/gilles/Code/PHP/sf2project/web&quot;&gt;
        Options Indexes FollowSymlinks
        AllowOverride All
        Order allow,deny
        Allow from all
    &lt;/Directory&gt;
    ErrorLog &quot;logs/sf2project-error_log&quot;
    CustomLog &quot;logs/sf2project-access_log&quot; common
&lt;/VirtualHost&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart the server.&lt;/p&gt;
&lt;p&gt;Open your browser and go to the URL: &lt;strong&gt;&lt;a href=&quot;http://project.localhost&quot;&gt;http://project.localhost&lt;/a&gt;&lt;/strong&gt;.
You should see the “Congratulations” page.&lt;/p&gt;
&lt;p&gt;To check if everything is correctly installed and configured, go the URL:
&lt;strong&gt;&lt;a href=&quot;http://project.localhost/check.php&quot;&gt;http://project.localhost/check.php&lt;/a&gt;&lt;/strong&gt;. If you see green everywhere,
you won. Otherwise,  fix any problem that it finds.&lt;/p&gt;

    </content>
    
    <category term="php" />
    
    <category term="symfony" />
    
    <category term="development" />
    
    <category term="macosx" />
    
  </entry>
  
  <entry xml:lang="en">
    <id>http://gillesfabio.com/blog/2010/12/17/getting-php-5-3-on-mac-os-x</id>
    <link href="http://gillesfabio.com/blog/2010/12/17/getting-php-5-3-on-mac-os-x" rel="alternate" type="text/html" />
    <title>Getting PHP 5.3 on Mac OS X</title>
    <published>2010-12-17T00:00:00.000Z</published>
    <updated>2010-12-17T00:00:00.000Z</updated>
    <author>
      <name>Gilles Fabio</name>
    </author>
    <content type="html">
      &lt;h2 id=&quot;the-default-mac-os-x-stack&quot;&gt;The default Mac OS X stack&lt;/h2&gt;
&lt;p&gt;If you are running Mac OS X Snow Leopard, you should already have PHP 5.3.3
installed on your system.&lt;/p&gt;
&lt;p&gt;To be sure, open &lt;strong&gt;Applications &amp;raquo; Utilities &amp;raquo; Terminal&lt;/strong&gt; and check it yourself:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/bin/php -v
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You just have to turn on the Apple’s Personal Web Sharing in the System
Preferences and place your projects in &lt;code&gt;$HOME/Sites&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;But you will need to install MySQL or any SGBDR/NoSQL Manager by hand (getting
the tarballs and fighting with dependencies, configuration and compilation stuff).
Installing Apache/PHP modules and libraries can also be a pain. Personally,
I rather prefer the MAMP solution or using package managers such as
&lt;a href=&quot;http://macports.org&quot;&gt;MacPorts&lt;/a&gt; or &lt;a href=&quot;https://github.com/mxcl/homebrew&quot;&gt;Homebrew&lt;/a&gt;.
To my point of view, the default Mac OS X stack is only suitable for simple needs.&lt;/p&gt;
&lt;h2 id=&quot;mamp-drag-drop-done-&quot;&gt;MAMP: Drag, drop, done.&lt;/h2&gt;
&lt;p&gt;This is the quick way to get a ready-to-go PHP 5.3 environment on a Mac.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.mamp.info/en/index.html&quot;&gt;Download MAMP&lt;/a&gt;, drag and drop the &lt;code&gt;MAMP&lt;/code&gt;
folder into the &lt;code&gt;Applications&lt;/code&gt; folder and that’s all.&lt;/p&gt;
&lt;p&gt;You can easily switch between two major PHP versions (PHP 5.2 or PHP 5.3) in a
single click thanks to the &lt;strong&gt;Applications &amp;raquo; MAMP &amp;raquo; MAMP&lt;/strong&gt; application.
As the default MAMP PHP version is 5.2, launch the &lt;strong&gt;MAMP&lt;/strong&gt; application, go in
the preferences pane and change the current PHP version.&lt;/p&gt;
&lt;p&gt;You now have to override your system &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Edit your &lt;code&gt;$HOME/.profile&lt;/code&gt; or &lt;code&gt;$HOME/.bash_profile&lt;/code&gt; file, then add these lines
at the end:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;export PATH=/Applications/MAMP/bin/php5.3/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Source it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;source $HOME/.profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check your PHP version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;php -v
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command should return &lt;code&gt;PHP 5.3.2 (cli)&lt;/code&gt; (or above).&lt;/p&gt;
&lt;h2 id=&quot;cook-your-own-stack-with-macports&quot;&gt;Cook your own stack with MacPorts&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://macports.org&quot;&gt;MacPorts&lt;/a&gt; is the most popular package manager for Mac OS X.
&lt;a href=&quot;https://github.com/mxcl/homebrew&quot;&gt;Homebrew&lt;/a&gt; is also very powerful. With MacPorts,
you can install your own stack with a high level of configuration and
optimization. Everything is compiled from source and MacPorts manages itself
all dependencies. Obviously, this takes time but it’s worth it. Let’s go?&lt;/p&gt;
&lt;p&gt;Turn off Apple’s Personal Web Sharing in the System Preferences.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.macports.org/install.php&quot;&gt;Install&lt;/a&gt; MacPorts.&lt;/p&gt;
&lt;p&gt;Open a terminal (&lt;strong&gt;Applications &amp;raquo; Utilities &amp;raquo; Terminal&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Update the ports tree:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo port -d selfupdate
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Install Apache2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo port install apache2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Activate Apache2 to start it automatically at the boot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo port load apache2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Go in your browser and enter the URL: &lt;strong&gt;&lt;a href=&quot;http://localhost&quot;&gt;http://localhost&lt;/a&gt;&lt;/strong&gt;. You should see “It works!”.&lt;/p&gt;
&lt;p&gt;Install MySQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo port install mysql5-server
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Set up the “mysql” database:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -u _mysql mysql_install_db5
sudo chown -R mysql:mysql /opt/local/var/db/mysql5/
sudo chown -R mysql:mysql /opt/local/var/run/mysql5/
sudo chown -R mysql:mysql /opt/local/var/log/mysql5/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Activate MySQL to start it automatically at the boot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo port load mysql5-server
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Set the MySQL’s root password (the current one is empty):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysqladmin5 -u root -p password &lt;your-password&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Try to log you as root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql5 -u root -p
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then exit the session:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql&gt; exit;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Secure the configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/opt/local/bin/mysql_secure_installation5
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Install PHP:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo port install php5 +apache2 +pear
sudo port install php5-mysql php5-sqlite php5-xdebug php5-mbstring php5-iconv \
php5-posix php5-apc
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Register PHP with Apache2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/local/apache2/modules
sudo /opt/local/apache2/bin/apxs -a -e -n &quot;php5&quot; libphp5.so
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Set up the PHP configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/local/etc/php5
sudo cp php.ini-development php.ini
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Set up the timezone in &lt;code&gt;/opt/local/etc/php5/php.ini&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-ini&quot;&gt;[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = &quot;Europe/Paris&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Set up the MySQL default socket:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;sudo -i
cd /opt/local/etc/php5
cp php.ini php.ini.bak
defSock=`/opt/local/bin/mysql_config5 --socket`
cat php.ini | sed \
  -e &quot;s#pdo_mysql\.default_socket.*#pdo_mysql\.default_socket=${defSock}#&quot; \
  -e &quot;s#mysql\.default_socket.*#mysql\.default_socket=${defSock}#&quot; \
  -e &quot;s#mysqli\.default_socket.*#mysqli\.default_socket=${defSock}#&quot; &gt; tmp.ini
grep default_socket tmp.ini
mv tmp.ini php.ini
rm php.ini.bak
exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit &lt;code&gt;/opt/local/apache2/conf/httpd.conf&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Find these lines:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-apache&quot;&gt;&lt;IfModule dir_module&gt;
    DirectoryIndex index.html
&lt;/IfModule&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, add &lt;code&gt;index.php&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-apache&quot;&gt;&lt;IfModule dir_module&gt;
    DirectoryIndex index.php index.html
&lt;/IfModule&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the end of the file, add this line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-apache&quot;&gt;Include conf/extra/mod_php.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Apache2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo /opt/local/apache2/bin/apachectl -k restart
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Done.&lt;/p&gt;
&lt;h2 id=&quot;adding-a-virtualhost&quot;&gt;Adding a VirtualHost&lt;/h2&gt;
&lt;p&gt;For a MacPorts configuration, edit &lt;code&gt;/opt/local/apache2/conf/httpd.conf&lt;/code&gt; and
uncomment this line:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-apache&quot;&gt;Include conf/extra/httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then edit &lt;code&gt;/opt/local/apache2/conf/extra/httpd-vhosts.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Remove dummy examples and replace them with your own configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-apache&quot;&gt;NameVirtualHost *:80

&lt;VirtualHost *:80&gt;
    DocumentRoot &quot;/absolute/path/to/your/project&quot;
    ServerName project.localhost
    &lt;Directory &quot;/absolute/path/to/your/project&quot;&gt;
        Options Indexes FollowSymlinks
        AllowOverride All
        Order allow,deny
        Allow from all
    &lt;/Directory&gt;
    ErrorLog &quot;logs/project-error_log&quot;
    CustomLog &quot;logs/project-access_log&quot; common
&lt;/VirtualHost&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Apache2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo /opt/local/apache2/bin/apachectl -k restart
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For a MAMP configuration, add your VirtualHost at the end of
&lt;code&gt;Applications/MAMP/conf/httpd.conf&lt;/code&gt; file, then restart the server.&lt;/p&gt;
&lt;p&gt;Don’t forget to add your host in &lt;code&gt;/private/etc/hosts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1    localhost project.localhost
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the folder of your project, create a &lt;code&gt;phpinfo.php&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-php&quot;&gt;&lt;?php phpinfo(); ?&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, open your browser and go to &lt;strong&gt;&lt;a href=&quot;http://project.localhost/phpinfo.php&quot;&gt;http://project.localhost/phpinfo.php&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You should see the PHP Info page.&lt;/p&gt;

    </content>
    
    <category term="php" />
    
    <category term="mysql" />
    
    <category term="apache" />
    
    <category term="macosx" />
    
    <category term="development" />
    
  </entry>
  
  <entry xml:lang="fr">
    <id>http://gillesfabio.com/blog/2010/12/16/python-et-les-decorateurs</id>
    <link href="http://gillesfabio.com/blog/2010/12/16/python-et-les-decorateurs" rel="alternate" type="text/html" />
    <title>Python et les décorateurs</title>
    <published>2010-12-16T00:00:00.000Z</published>
    <updated>2010-12-16T00:00:00.000Z</updated>
    <author>
      <name>Gilles Fabio</name>
    </author>
    <content type="html">
      &lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Vous débutez en programmation &lt;a href=&quot;http://python.org&quot;&gt;Python&lt;/a&gt; ? Python vous
intéresse ? Vous avez certainement déjà entendu parler des “décorateurs”.
C’est une fonctionnalité très utilisée dans le monde Python. Ils permettent
d’écrire du code concis, lisible et non-répétitif.&lt;/p&gt;
&lt;p&gt;Concrètement, un décorateur est un &lt;a href=&quot;http://gillesfabio.com/blog/2010/07/31/python-et-les-callables/&quot;&gt;callable&lt;/a&gt;
qui prend pour argument un &lt;em&gt;callable&lt;/em&gt; et qui retourne une copie de ce même
&lt;em&gt;callable&lt;/em&gt; en le “décorant”, c’est-à-dire en effectuant un pré-traitement et/ou
un post-traitement sur celui-ci. Dans le jargon, on parle souvent de &lt;em&gt;wrapping&lt;/em&gt;
(on enveloppe, on saupoudre, on apporte un “plus”, on décore).&lt;/p&gt;
&lt;p&gt;Si vous connaissez un peu le framework web &lt;a href=&quot;http://djangoproject.com&quot;&gt;Django&lt;/a&gt;,
vous avez peut-être déjà manipulé des décorateurs, notamment pour
&lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/auth/&quot;&gt;la gestion de l’authentification&lt;/a&gt;.
Pour protéger les vues, il suffit de les décorer avec les décorateurs
&lt;code&gt;@login_required&lt;/code&gt; ou  &lt;code&gt;@permission_required&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Un exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@login_required
def protected_view(request):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans cet exemple, la vue &lt;code&gt;protected_view&lt;/code&gt; est automatiquement protégée. Si vous
n’êtes pas un utilisateur enregistré et connecté, vous serez redirigé vers le
formulaire d’authentification, puisque le décorateur &lt;code&gt;@login_required&lt;/code&gt; a ajouté
son grain de sel pour que nous écrivions le moins de code possible.&lt;/p&gt;
&lt;p&gt;Le micro-framework web &lt;a href=&quot;http://bottle.paws.de/&quot;&gt;Bottle&lt;/a&gt; utilise essentiellement
cette fonctionnalité du langage.&lt;/p&gt;
&lt;p&gt;Un exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@route(&#39;/hello/:names&#39;)
@view(&#39;hello&#39;)
def hello(names):
   names = names.split(&#39;,&#39;)
   return dict(title=&#39;Hello World&#39;, names=names)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ici, le décorateur &lt;code&gt;@route&lt;/code&gt; se charge de définir une route pour cette vue et
le décorateur &lt;code&gt;@view&lt;/code&gt; se charge de charger le template &lt;code&gt;hello.tpl&lt;/code&gt; et d’y
injecter les variables retournées dans son contexte. Comme vous pouvez le
constater, nous pouvons exécuter plusieurs décorateurs sur une même
fonction car nous pouvons les “chaîner”.&lt;/p&gt;
&lt;p&gt;Il existe deux types de syntaxes.&lt;/p&gt;
&lt;p&gt;La syntaxe arobase &lt;code&gt;@&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@dec2
@dec1
def func(arg1, arg2):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et la syntaxe classique :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def func(arg1, arg2):
    pass

func = dec2(dec1(func))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Les deux exemples de code ci-dessus sont équivalents. On utilise, généralement,
la syntaxe &lt;code&gt;@&lt;/code&gt; lorsque nous souhaitons “figer” le comportement de la fonction
(lors de son appel, ses décorateurs seront alors systématiquement exécutés) et
la syntaxe classique si nous n’avons pas accés à sa définition ou lorsqu’on
souhaite modifier son comportement à un endroit précis dans le code.&lt;/p&gt;
&lt;p&gt;Les décorateurs peuvent prendre des arguments requis ou optionnels, en plus
de la fonction à décorer. On peut alors modifier dynamiquement leur
comportement. C’est le cas dans l’exemple du framework web Bottle avec les
décorateurs &lt;code&gt;@route&lt;/code&gt; et &lt;code&gt;@view&lt;/code&gt;. C’est aussi le cas pour les décorateurs
&lt;code&gt;@login_required&lt;/code&gt; ou &lt;code&gt;@permission_required&lt;/code&gt; de Django.&lt;/p&gt;
&lt;p&gt;Un exemple avec &lt;code&gt;@permission_required&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@permission_required(&#39;polls.can_vote&#39;, login_url=&#39;/loginpage/&#39;)
def my_view(request):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Non seulement de prendre pour argument la fonction à décorer, ce décorateur
prend aussi un argument requis (la permission) et un argument optionnel
(l’URL du formulaire d’authentification en cas d’échec).&lt;/p&gt;
&lt;p&gt;Maintenant que nous connaissons un peu mieux les décorateurs, passons à la
pratique. Nous allons, tout d’abord, implémenter un décorateur “simple” ne
prenant pas d’argument. Nous aborderons ensuite l’implémentation de
décorateurs plus élaborés, capables d’accepter des arguments (requis et/ou
optionnels). Nous ferons ensuite connaissance avec &lt;code&gt;functools.wraps&lt;/code&gt; (inclus
dans Python). Et nous terminerons avec un exemple concret.&lt;/p&gt;
&lt;h2 id=&quot;d-corateur-simple&quot;&gt;Décorateur simple&lt;/h2&gt;
&lt;p&gt;Commençons par un premier exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

# Notre décorateur
def decorate(func):
    print u&quot;Je suis dans la fonction &#39;decorate&#39; et je décore &#39;%s&#39;.&quot; % func.__name__
    print u&quot;Exécution de la fonction &#39;%s&#39;.&quot; % func.__name__
    return func

# Notre fonction décorée
@decorate
def foobar(*args):
    print &quot;, &quot;.join(args)

# Appel de la fonction
foobar(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exécutons le code :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &#39;decorate&#39; et je décore &#39;foobar&#39;.
Exécution de la fonction &#39;foobar&#39;.
A, B, C, D
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notre fonction a bien été décorée. Mais dans cet exemple, nous avons pas accès
aux arguments de la fonction décorée. On affiche simplement une chaîne de
caractères pour s’informer de l’éxecution du décorateur.&lt;/p&gt;
&lt;p&gt;Modifions notre code pour pouvoir accéder aux arguments de la fonction décorée :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

# Notre décorateur
def decorate(func):
    print u&quot;Je suis dans la fonction &#39;decorate&#39; et je décore &#39;%s.&#39;&quot; % func.__name__
    def wrapper(*args, **kwargs):
        print u&quot;Je suis dans la fonction &#39;wrapper&#39; qui accède aux arguments de &#39;%s&#39;.&quot; % func.__name__
        a = list(args)
        a.reverse()
        print u&quot;Je t&#39;en donne la preuve, je peux les inverser : %s.&quot; % &#39;, &#39;.join(a)
        print u&quot;Exécution de la fonction &#39;%s&#39;.&quot; % func.__name__
        return func(*args, **kwargs)
    return wrapper

# Notre fonction décorée
@decorate
def foobar(*args):
    print &quot;, &quot;.join(args)

# Appel de la fonction
foobar(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exécutons-le :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &#39;decorate&#39; et je décore &#39;foobar.&#39;
Je suis dans la fonction &#39;wrapper&#39; qui accède aux arguments de &#39;foobar&#39;.
Je t&#39;en donne la preuve, je peux les inverser : D, C, B, A.
Exécution de la fonction &#39;foobar&#39;.
A, B, C, D
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nous avons créé un &lt;em&gt;wrapper&lt;/em&gt;, qui n’est autre qu’une fonction possédant la
même signature (les mêmes arguments) que la fonction à décorer et qui retourne
la fonction décorée, avec ses arguments. Le &lt;em&gt;wrapper&lt;/em&gt; est lui-même retourné par
le décorateur. C’est un système à la &lt;em&gt;poupées Russes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Dans cet exemple, nous avons seulement effectué un pré-traitement. Mais il est
tout à fait possible, très simplement, d’effectuer un post-traitement sur la
fonction à décorer :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

# Notre décorateur
def decorate(func):
    print u&quot;Je suis dans la fonction &#39;decorate&#39; et je décore &#39;%s.&#39;&quot; % func.__name__
    def wrapper(*args, **kwargs):
        print u&quot;Je suis dans la fonction &#39;wrapper&#39; qui accède aux arguments de &#39;%s&#39;.&quot; % func.__name__
        a = list(args)
        a.reverse()
        print u&quot;J&#39;en donne la preuve, je peux les inverser : %s.&quot; % &#39;, &#39;.join(a)
        print u&quot;Exécution de la fonction &#39;%s&#39;.&quot; % func.__name__
        response = func(*args)
        print u&quot;Je peux effectuer, ici, un post-traitement.&quot;
        return response
    return wrapper

# Notre fonction décorée
@decorate
def foobar(*args):
    print &quot;, &quot;.join(args)

# Appel de la fonction
foobar(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exécutons le code :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &#39;decorate&#39; et je décore &#39;foobar.&#39;
Je suis dans la fonction &#39;wrapper&#39; qui accède aux arguments de &#39;foobar&#39;.
Je t&#39;en donne la preuve, je peux les inverser : D, C, B, A.
Exécution de la fonction &#39;foobar&#39;.
A, B, C, D
Je peux effectuer, ici, un post-traitement.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour simplifier au maximum :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def decorate(func):
    def wrapper(*args, **kwargs):
        # Pré-traitement
        response = func(*args, **kwargs)
        # Post-traitement
        return response
    return wrapper
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ici, je récupère le retour de la fonction dans &lt;code&gt;response&lt;/code&gt; et je retourne
ensuite la valeur après le post-traitement. Mais si vous n’avez pas besoin
d’accéder aux données retournées par la fonction à décorer, ce qui était un
peu notre cas, il suffit juste d’appeller la fonction :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def decorate(func):
    def wrapper(*args, **kwargs):
        # Pré-traitement
        func(*args, **kwargs)
        # Post-traitement
    return wrapper
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cela dépend vraiment de votre implémentation.&lt;/p&gt;
&lt;p&gt;C’est un peu plus clair ? Si vous avez compris les exemples ci-dessus et ce
bout de code, vous avez tout compris.&lt;/p&gt;
&lt;p&gt;J’entends certains penser fortement… Si un décorateur prend pour argument un
&lt;em&gt;callable&lt;/em&gt;, on doit pouvoir lui passer d’autres arguments, non ? Si, par
exemple, on souhaite modifier son comportement dynamiquement ? Comme pour le
décorateur &lt;code&gt;@permission_required&lt;/code&gt; de Django, qui prend pour arguments une
permission donnée et, éventuellement, l’URL de la page de connexion pour
rediriger automatiquement l’utilisateur en cas d’échec :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@permission_required(&#39;polls.can_vote&#39;, login_url=&#39;/loginpage/&#39;)
def my_view(request):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ou bien, comme avec les vues de Bottle et leurs décorateurs &lt;code&gt;@route&lt;/code&gt; et &lt;code&gt;@view&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@route(&#39;/hello/:names&#39;)
@view(&#39;hello&#39;)
def hello(names):
   names = names.split(&#39;,&#39;)
   return dict(title=&#39;Hello World&#39;, names=names)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bien vu. Nous avons abordé l’implémentation d’un décorateur simple qui ne
prend pas d’argument, excepté le &lt;em&gt;callable&lt;/em&gt; à décorer. On ne peut donc pas
modifier dynamiquement son comportement en précisant diverses options. Voyons
comment implémenter un décorateur capable d’accepter des arguments.&lt;/p&gt;
&lt;h2 id=&quot;d-corateur-avec-arguments&quot;&gt;Décorateur avec arguments&lt;/h2&gt;
&lt;p&gt;Pour implémenter un décorateur dynamique capable d’accépter des arguments
(ou “options”), nous devons ajouter un niveau supplémentaire : un décorateur
qui prend pour arguments les arguments du décorateur de la fonction à décorer.
Vous suivez toujours ? Pour un décorateur simple, nous avons deux niveaux de
fonctions (le décorateur et le &lt;em&gt;wrapper&lt;/em&gt;). Pour un décorateur avec arguments,
nous en avons trois (le décorateur du décorateur, le décorateur et
le &lt;em&gt;wrapper&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Si on simplifie au maxium :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def decorate(arg1, arg2, arg3):
    def decorated(func):
        def wrapper(*args, **kwargs):
            # Pré-traitement
            response = func(*args, **kwargs)
            # Post-traitement
            return response
        return wrapper
    return decorated
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prenons un exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

def decorate(arg1, arg2, arg3):
    print u&#39;Je suis dans la fonction &quot;decorate&quot;.&#39;
    def decorated(func):
        print u&#39;Je suis dans la fonction &quot;decorated&quot;.&#39;
        def wrapper(*args, **kwargs):
            print u&#39;Je suis dans la fonction &quot;wrapper&quot;.&#39;
            print u&quot;Les arguments du décorateurs sont : %s, %s, %s.&quot; % (arg1, arg2, arg3)
            print u&quot;Pré-traitement.&quot;
            print u&quot;Exécution de la fonction %s.&quot; % func.__name__
            response = func(*args, **kwargs)
            print u&quot;Post-traitement.&quot;
            return response
        return wrapper
    return decorated

@decorate(&quot;Arg 1&quot;, &quot;Arg 2&quot;, &quot;Arg 3&quot;)
def foobar():
    print u&quot;Je suis foobar, je vous reçois 5 sur 5.&quot;

foobar()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si on exécute le code :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &quot;decorate&quot;.
Je suis dans la fonction &quot;decorated&quot;.
Je suis dans la fonction &quot;wrapper&quot;.
Les arguments du décorateurs sont : Arg 1, Arg 2, Arg 3.
Pré-traitement.
Exécution de la fonction foobar.
Je suis foobar, je vous reçois 5 sur 5.
Post-traitement.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Les arguments passés au décorateur ont bien été pris en compte.&lt;/p&gt;
&lt;p&gt;Nous avons implémenté un décorateur avec arguments obligatoires (ou requis).
Pour passer des arguments optionnels :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def decorate(arg1=&#39;default&#39;, arg2=None, arg3=None):
    def decorated(func):
        def wrapper(*args, **kwargs):
            # Pré-traitement
            response = func(*args, **kwargs)
            # Post-traitement
            return response
        return wrapper
    return decorated

@decorate(arg1=&#39;my value&#39;)
def foobar():
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Attention. Comme tous les arguments de ce décorateur sont optionnels, si on ne
souhaite pas les modifier, on serait tenté d’appeller le décorateur comme un
décorateur “simple” :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@decorate
def foobar():
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si on exécute ce code :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-pycon&quot;&gt;Traceback (most recent call last):
  File &quot;exemple.py&quot;, line 22, in &lt;module&gt;
    foobar()
TypeError: decorated() takes exactly 1 argument (0 given)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Catastrophe ! Python vous demandera quand même de founir un argument, qu’il
considère comme “requis” (même si tous les arguments sont optionnels). Dans ce
cas, la bonne syntaxe est d’utiliser les parenthèses d’appel de fonction :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;@decorate()
def foobar():
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ce n’est pas très sexy, source d’erreur, mais ça fonctionne. Nous aborderons
plus loin comment contourner ce “problème”.&lt;/p&gt;
&lt;p&gt;Un autre exemple, avec un argument obligatoire et des arguments optionnels :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def decorate(arg1, arg2=&#39;default value&#39;, arg3=None):
    def decorated(func):
        def wrapper(*args, **kwargs):
            # Pré-traitement
            response = func(*args, **kwargs)
            # Post-traitement
            return response
        return wrapper
    return decorated

@decorate(&#39;required value&#39;)
def foo():
    pass

@decorate(&#39;required value&#39;, arg3=&#39;my value&#39;)
def bar():
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Retenez surtout les trois niveaux de fonctions (la fonction qui prend pour
arguments les arguments du décorateur, le décorateur et le &lt;em&gt;wrapper&lt;/em&gt;) et
les parenthèses d’appel de fonction pour un décorateur avec arguments
optionnels appelé sans argument.&lt;/p&gt;
&lt;h2 id=&quot;pr-servation-de-la-fonction-d-cor-e&quot;&gt;Préservation de la fonction décorée&lt;/h2&gt;
&lt;p&gt;Jusque là, on s’est intéressé au décorateur, pas vraiment à la fonction
décorée.&lt;/p&gt;
&lt;p&gt;Penchons-nous de plus près sur la fonction décorée :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

def logged(func):
    def wrapper(*args, **kwargs):
        &quot;&quot;&quot;Je suis la documentation de logged.&quot;&quot;&quot;
        print u&quot;Exécution de %s.&quot; % func.__name__
        return func(*args, **kwargs)
    return wrapper

@logged
def foobar(arg):
    &quot;&quot;&quot;Je suis la documentation de foobar.&quot;&quot;&quot;
    print arg

foobar(&quot;Yeah!&quot;)

print foobar.__name__
print foobar.__doc__
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si on exécute ce bout de code :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Exécution de foobar.
Yeah!
_logged
Je suis la documentation de logged.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mince ! On appelle la fonction &lt;code&gt;foobar&lt;/code&gt; et c’est pourtant le nom et la
documentation du décorateur &lt;code&gt;logged&lt;/code&gt; que nous renvoie &lt;code&gt;foobar.__name__&lt;/code&gt; et
&lt;code&gt;foobar.__doc__&lt;/code&gt;. Ça, c’est bien le genre de truc à donner des migraines. Si
on y réfléchie bien, c’est un comportement logique puisque la fonction
retournée par le décorateur est une fonction de substitution, pas notre
vraie &lt;code&gt;foobar&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Heureusement, si vous utilisez Python 2.5 ou supérieure, &lt;code&gt;functools.wraps&lt;/code&gt;
vient à notre rescousse. Ce décorateur va préserver notre fonction décorée avec
élégance et délicatesse. Pour ce faire, nous devons juste décorer le &lt;em&gt;wrapper&lt;/em&gt;
avec ce décorateur, en lui passant comme argument la fonction à décorer.
Ensuite, il se charge du reste.&lt;/p&gt;
&lt;p&gt;Un exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
import functools

def logged(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        &quot;&quot;&quot;Je suis la documentation de logged.&quot;&quot;&quot;
        print u&quot;Exécution de %s.&quot; % func.__name__
        return func(*args, **kwargs)
    return wrapper

def foobar(arg):
    &quot;&quot;&quot;Je suis la documentation de foobar.&quot;&quot;&quot;
    print arg

foobar = logged(foobar)
foobar(&quot;Yeah!&quot;)

print foobar.__name__
print foobar.__doc__
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si on exécute ce code :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Exécution de foobar.
Yeah!
foobar
Je suis la documentation de foobar.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hallelujah ! Ça marche.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Si vous n’utilisez pas Python 2.5 ou supérieur mais si vous utilisez Django et
Python 2.4, vous pouvez quand même bénéficier de cette fonctionnalité via
&lt;code&gt;django.utils.functional.wraps&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;d-corateurs-avec-ou-sans-arguments&quot;&gt;Décorateurs avec ou sans arguments&lt;/h2&gt;
&lt;p&gt;Nous avons vu, plus haut, comment implémenter un décorateur avec arguments
obligatoires ou optionnels. Nous nous sommes aperçu que si nous appellons un
décorateur ne prenant que des arguments optionnels sans argument, comme un
décorateur “simple”, Python renvoie une erreur. En ajoutant les parenthèses
d’appel de fonction, on contourne le problème.&lt;/p&gt;
&lt;p&gt;Exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

def decorate(arg1=&#39;default&#39;, arg2=None, arg3=None):
    def decorated(func):
        def wrapper(*args, **kwargs):
            # Pré-traitement
            response = func(*args, **kwargs)
            # Post-traitement
            return response
        return wrapper
    return decorated

# MAUVAIS
@decorate
def foobar():
    pass

# BON
@decorate()
def foobar():
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Malheureusement, il n’existe pas de solution générique. Pas de solution idéale
qui nous permettrait d’implémenter des décorateurs capables d’être appelés
avec ou sans arguments, sans parenthèses d’appel de fonction. Un décorateur ne
prend pas d’argument ? Pas de paranthèses. Un décorateur prend des arguments
optionnels, même avec des valeurs par défaut ? Lorsqu’il est appelé seul, les
parenthèses s’imposent. Pour un décorateur avec des arguments obligatoires, on
ne se pose même pas la question puisque nous sommes “obligé” de lui donner des
arguments.&lt;/p&gt;
&lt;p&gt;Cependant, si il n’existe pas de solution “générique”, nous pouvons quand
même modifier notre implémentation pour éviter l’emploi des parenthèses.
Nous devons alors donner notre fonction à décorer comme premier argument
au décorateur de notre décorateur. On l’initialisera à &lt;code&gt;None&lt;/code&gt;. Si nous
fournissons des arguments optionnels, la valeur de notre fonction sera &lt;code&gt;None&lt;/code&gt;,
on retournera alors une fonction prenant pour argument notre fonction à décorer
et qui retournera notre décorateur. Si la valeur de notre fonction n’est pas
&lt;code&gt;None&lt;/code&gt;, cela sous-entend que nous appelons notre décorateur sans argument, ni
parenthèses.&lt;/p&gt;
&lt;p&gt;Pour mieux comprendre, modifions notre exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

def decorate(func=None, arg1=None, arg2=None, arg3=None):
    print u&#39;Je suis dans la fonction &quot;decorate&quot;.&#39;
    def decorated(func):
        print u&#39;Je suis dans la fonction &quot;decorated&quot;.&#39;
        def wrapper(*args, **kwargs):
            print u&#39;Je suis dans la fonction &quot;wrapper&quot;.&#39;
            print u&quot;Les arguments du décorateurs sont : %s, %s, %s.&quot; % (arg1, arg2, arg3)
            print u&quot;Pré-traitement.&quot;
            print u&quot;Exécution de la fonction %s.&quot; % func.__name__
            response = func(*args, **kwargs)
            print u&quot;Post-traitement.&quot;
            return response
        return wrapper
    if func is None:
        # Le décorateur est appellé avec des arguments
        def decorator(func):
            return decorated(func)
        return decorator
    # Le décorateur est appellé sans arguments
    return decorated(func)

@decorate
def foobar():
    print u&quot;Je suis foobar, je vous reçois 5 sur 5.&quot;

foobar()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si on exécute ce code :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &quot;decorate&quot;.
Je suis dans la fonction &quot;decorated&quot;.
Je suis dans la fonction &quot;wrapper&quot;.
Les arguments du décorateurs sont : None, None, None.
Pré-traitement.
Exécution de la fonction foobar.
Je suis foobar, je vous reçois 5 sur 5.
Post-traitement.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ça marche ! Nous n’avons plus besoin d’ajouter les parenthèses à notre
décorateur quand il est appelé sans argument. Mais, en contre-partie, cela nous
contraint à lui donner uniquement des arguments nommés :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# BON
@decorate(arg1=&quot;Python&quot;, arg2=&quot;Haskell&quot;)
def foobar():
    print u&quot;Je suis foobar, je vous reçois 5 sur 5.&quot;

# MAUVAIS
@decorate(&quot;Python&quot;, &quot;Haskell&quot;)
def foobar():
    print u&quot;Je suis foobar, je vous reçois 5 sur 5.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si on exécute le second exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &quot;decorate&quot;.
Je suis dans la fonction &quot;decorated&quot;.
Je suis dans la fonction &quot;wrapper&quot;.
Les arguments du décorateurs sont : Haskell, None, None.
Pré-traitement.
Traceback (most recent call last):
  File &quot;exemple.py&quot;, line 24, in &lt;module&gt;
    @decorate(&quot;Python&quot;, &quot;Haskell&quot;)
  File &quot;exemple.py&quot;, line 11, in wrapper
    print u&quot;Exécution de la fonction %s.&quot; % func.__name__
AttributeError: &#39;str&#39; object has no attribute &#39;__name__&#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ça coince. Notre chaîne de caractères “Python” a été interprétée comme
la fonction à décorer, puisqu’elle a été passée comme premier argument, donc
logiquement considérée comme &lt;code&gt;func&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Vous vous souvenez du chapitre précédent ? Notre code ne préserve pas la
fonction décorée. Rajoutons un peu de documentation et appelons &lt;code&gt;foobar.__name__&lt;/code&gt;
et &lt;code&gt;foobar.__doc__&lt;/code&gt; pour s’en convaincre :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

def decorate(func=None, arg1=None, arg2=None, arg3=None):
    print u&#39;Je suis dans la fonction &quot;decorate&quot;.&#39;
    def decorated(func):
        print u&#39;Je suis dans la fonction &quot;decorated&quot;.&#39;
        def wrapper(*args, **kwargs):
            &quot;&quot;&quot;Je suis la documentation de decorate.&quot;&quot;&quot;
            print u&#39;Je suis dans la fonction &quot;wrapper&quot;.&#39;
            print u&quot;Les arguments du décorateurs sont : %s, %s, %s.&quot; % (arg1, arg2, arg3)
            print u&quot;Pré-traitement.&quot;
            print u&quot;Exécution de la fonction %s.&quot; % func.__name__
            response = func(*args, **kwargs)
            print u&quot;Post-traitement.&quot;
            return response
        return wrapper
    if func is None:
        # Le décorateur est appellé avec des arguments
        def decorator(func):
            return decorated(func)
        return decorator
    # Le décorateur est appellé sans arguments
    return decorated(func)

@decorate(arg1=&quot;Python&quot;, arg2=&quot;Haskell&quot;)
def foobar():
    &quot;&quot;&quot;Je suis la documentation de foobar.&quot;&quot;&quot;
    print u&quot;Je suis foobar, je vous reçois 5 sur 5.&quot;

print foobar.__name__
print foobar.__doc__
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exécutons :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &quot;decorate&quot;.
Je suis dans la fonction &quot;decorated&quot;.
wrapper
Je suis la documentation de decorate.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mauvais. Faisons appel à &lt;code&gt;functools.wraps&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
import functools

def decorate(func=None, arg1=None, arg2=None, arg3=None):
    print u&#39;Je suis dans la fonction &quot;decorate&quot;.&#39;
    def decorated(func):
        print u&#39;Je suis dans la fonction &quot;decorated&quot;.&#39;
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            &quot;&quot;&quot;Je suis la documentation de decorate.&quot;&quot;&quot;
            print u&#39;Je suis dans la fonction &quot;wrapper&quot;.&#39;
            print u&quot;Les arguments du décorateurs sont : %s, %s, %s.&quot; % (arg1, arg2, arg3)
            print u&quot;Pré-traitement.&quot;
            print u&quot;Exécution de la fonction %s.&quot; % func.__name__
            response = func(*args, **kwargs)
            print u&quot;Post-traitement.&quot;
            return response
        return wrapper
    if func is None:
        # Le décorateur est appellé avec des arguments
        def decorator(func):
            return decorated(func)
        return decorator
    # Le décorateur est appellé sans arguments
    return decorated(func)

@decorate(arg1=&quot;Python&quot;, arg2=&quot;Haskell&quot;)
def foobar():
    &quot;&quot;&quot;Je suis la documentation de foobar.&quot;&quot;&quot;
    print u&quot;Je suis foobar, je vous reçois 5 sur 5.&quot;

print foobar.__name__
print foobar.__doc__
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exécutons :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Je suis dans la fonction &quot;decorate&quot;.
Je suis dans la fonction &quot;decorated&quot;.
foobar
Je suis la documentation de foobar.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;C’est beaucoup mieux.&lt;/p&gt;
&lt;h2 id=&quot;exemple-concret-avec-django&quot;&gt;Exemple concret avec Django&lt;/h2&gt;
&lt;p&gt;Vous connaissez la fonction &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/http/shortcuts/#render-to-response&quot;&gt;render_to_response&lt;/a&gt;
de Django ? C’est un raccourci pour rendre du contenu dans un template. Cette
fonction prend pour arguments : le nom du template (requis), un dictionnaire
contenant les variables/valeurs à ajouter dans le contexte du template
(optionnel), une instance de &lt;code&gt;Context&lt;/code&gt; ou &lt;code&gt;RequestContext&lt;/code&gt; (si on souhaite
bénéficier des &lt;em&gt;context processors&lt;/em&gt;) dans laquelle sera injecté le dictionnaire
(par défaut, une instance de &lt;code&gt;Context&lt;/code&gt;) et le type MIME de la réponse (par
défaut, celui défini par &lt;code&gt;settings.DEFAULT_CONTENT_TYPE&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response

def home(request):
    &quot;&quot;&quot;
    Page d&#39;accueil.
    &quot;&quot;&quot;
    return render_to_response(&#39;home.html&#39;, {&#39;foo&#39;: &#39;bar&#39;})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans cet exemple, nous n’accédons pas aux &lt;em&gt;context processors&lt;/em&gt; de Django. Pour
pour avoir accéder à ces fameux &lt;em&gt;context processors&lt;/em&gt;, nous devons plutôt
utiliser une instance &lt;code&gt;RequestContext&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
from django.shortcuts import render_to_response
from django.template import RequestContext

def home(request):
    &quot;&quot;&quot;
    Page d&#39;accueil.
    &quot;&quot;&quot;
    return render_to_response(
        &#39;home.html&#39;,
        {&quot;foo&quot;: &quot;bar&quot;},
        context_instance=RequestContext(request))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On est bien d’accord, c’est un peu verbeux. Si vous êtes un développeur Django
rusé, vous utilisez certainement les vues génériques (nous allons y venir).
Comme nous ne sommes pas censé savoir qu’il existe déjà une solution
prête-à-l’emploi, implémentons un décorateur pour rendre ce code un peu plus
concis. Nous allons le nommer &lt;code&gt;render&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Implémentation :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
import functools

from django.shortcuts import render_to_response
from django.template import RequestContext

def render(template):
    def decorated(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            &quot;&quot;&quot;
            Raccourci du raccourci ``django.shortcuts.render_to_response``.
            &quot;&quot;&quot;
            context = func(*args, **kwargs)
            return render_to_response(
                template,
                context,
                context_instance=RequestContext(args[0]))
        return wrapper
    return decorated

@render(&#39;home.html&#39;)
def home(request):
    &quot;&quot;&quot;
    Page d&#39;accueil.
    &quot;&quot;&quot;
    return {
        &#39;title&#39;: u&quot;Home&quot;,
        &#39;description&#39;: u&quot;This is the homepage.&quot;,
        &#39;view_name&#39;: home.__name__,
        &#39;view_doc&#39;: home.__doc__,
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nos vues n’ont plus qu’à retourner un dictionnaire des variables/valeurs à
injecter dans le context du template, que nous donnons comme argument au
décorateur. Notre décorateur &lt;code&gt;render&lt;/code&gt; se charge du reste. Les variables
&lt;code&gt;view_name&lt;/code&gt; et &lt;code&gt;view_doc&lt;/code&gt; doivent respectivement retourner le nom et la
documentation de la vue, et non celles du décorateur. Merci &lt;code&gt;functools.wrap&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Oui mais… On a réinventé la roue. Django propose déjà des
&lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/generic-views/#topics-generic-views&quot;&gt;vues génériques&lt;/a&gt;,
dont notamment &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/ref/generic-views/#django-views-generic-simple-direct-to-template&quot;&gt;direct_to_template&lt;/a&gt;.
Nous n’avons, d’ailleurs, même plus besoin d’écrire la moindre vue. On spécifie
tout ça dans notre module &lt;code&gt;urls.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Notre code se limite à :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;urlpatterns = patterns(&#39;&#39;,
    url(r&#39;^foobar/$&#39;, &#39;django.views.generic.simple.direct_to_template&#39;, {
        &#39;template&#39;: &#39;foobarapp/index.html&#39;,
        &#39;extra_context&#39;: {
            &quot;foo&quot;: &quot;bar&quot;,
        }},
        name=&quot;foobar&quot;,
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Merci les vues génériques. Il est vivement recommandé d’en abuser.&lt;/p&gt;
&lt;h2 id=&quot;un-peu-de-lecture&quot;&gt;Un peu de lecture&lt;/h2&gt;
&lt;p&gt;Pour en savoir plus, n’hésitez pas à consulter ces liens :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.python.org/moin/PythonDecorators&quot;&gt;Python Decorators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.python.org/dev/peps/pep-0318/&quot;&gt;PEP 318 — Decorators for Functions and Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://wiki.python.org/moin/PythonDecoratorLibrary&quot;&gt;Python Decorator Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://developers.freshbooks.com/blog/view/logging_actions_with_python_decorators_part_i_decorating_logged_functions/&quot;&gt;Freshbooks Blog — Logging actions with Python decorators (part 1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://developers.freshbooks.com/blog/view/logging_actions_with_python_decorators_part_ii_decorating_logged_functions/&quot;&gt;Freshbooks Blog — Logging actions with Python decorators (part 1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/739654/understanding-python-decorators&quot;&gt;Stackoverflow — Understanding Python Decorators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.ibm.com/developerworks/linux/library/l-cpdecor.html&quot;&gt;IBM — Charming Python: Decorators make magic easy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.artima.com/weblogs/viewpost.jsp?thread=240808&quot;&gt;Artima — Decorators I&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.artima.com/weblogs/viewpost.jsp?thread=240845&quot;&gt;Artima — Decorators II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.artima.com/weblogs/viewpost.jsp?thread=241209&quot;&gt;Artima — Decorators III&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.siafoo.net/article/68&quot;&gt;Siafoo — Python Decorators Don’t Have to be (that) Scary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.valuedlessons.com/2008/11/easy-python-decorators-with-decorator.html&quot;&gt;Valued Lessons — Easy Python Decorators with the decorator decorator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://personalpages.tds.net/~kent37/kk/00001.html&quot;&gt;Kent’s Korner — Decorators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.elfsternberg.com/2009/11/20/python-decorators-with-arguments-with-bonus-django-goodness/&quot;&gt;Elf Sternberg — Python: Decorators With Arguments (with bonus Django goodness)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://micheles.googlecode.com/hg/decorator/documentation.html&quot;&gt;Michele Simionato — The decorator module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://uswaretech.com/blog/2009/06/understanding-decorators/&quot;&gt;The Usware Blog — Understanding decorators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.gawel.org/howtos/python-decorators&quot;&gt;Gawel.org — Les décorateurs Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Les décorateurs sont une fonctionnalité très puissante et très utilisée du
langage Python. Python, lui-même, en propose de nombreux. Nous n’avons
qu’abordé &lt;code&gt;functools.wraps&lt;/code&gt;, mais il en existe beaucoup d’autres que vous
rencontrerez certainement un jour dans vos projets. Cet article n’est qu’une
simple introduction tant les possibilités offertes sont larges. Ils sont un
point fort du langage, avec sa syntaxe et son écosystème. Ne vous en privez
pas. À vos décorateurs !&lt;/p&gt;

    </content>
    
    <category term="python" />
    
    <category term="development" />
    
    <category term="django" />
    
  </entry>
  
  <entry xml:lang="fr">
    <id>http://gillesfabio.com/blog/2010/07/31/python-et-les-callables</id>
    <link href="http://gillesfabio.com/blog/2010/07/31/python-et-les-callables" rel="alternate" type="text/html" />
    <title>Python et les &quot;callables&quot;</title>
    <published>2010-07-31T00:00:00.000Z</published>
    <updated>2010-07-31T00:00:00.000Z</updated>
    <author>
      <name>Gilles Fabio</name>
    </author>
    <content type="html">
      &lt;p&gt;Tout développeur &lt;a href=&quot;http://python.org&quot;&gt;Python&lt;/a&gt; s’est certainement en jour posé
cette question : qu’est-ce qu’un &lt;em&gt;callable&lt;/em&gt; au sens Python ? Tout objet
“exécutable” ou “appelable” ? Flou. Vague. En Python, les fonctions et les
méthodes sont “naturellement” des &lt;em&gt;callables&lt;/em&gt; puisqu’elles sont définies pour
être appelées. On dit d’ailleurs qu’un objet &lt;em&gt;callable&lt;/em&gt; est un objet qui peut
être appelé comme une fonction (avec des parenthèses et prenant, éventuellement,
des arguments).&lt;/p&gt;
&lt;p&gt;Si l’on se réfère à la &lt;a href=&quot;http://docs.python.org/reference/datamodel.html&quot;&gt;documentation officielle&lt;/a&gt;,
sont &lt;em&gt;callables&lt;/em&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les fonctions et méthodes que nous définissons (&lt;em&gt;user-defined&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Les fonctions et méthodes natives (&lt;em&gt;built-in&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Les fonctions &lt;em&gt;generator&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Les classes &lt;em&gt;new-style&lt;/em&gt; ou &lt;em&gt;old-style&lt;/em&gt; (nommées aussi &lt;em&gt;classic&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Les instances de classe possédant une méthode &lt;code&gt;__call__&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En effet, Python ne limite pas qu’aux fonctions, classes et méthodes. Un
&lt;em&gt;callable&lt;/em&gt; est aussi tout objet qui possède une méthode spécifique
nommée &lt;code&gt;__call__&lt;/code&gt;. Pour être plus précis : un objet &lt;em&gt;dont le type&lt;/em&gt; possède une
méthode spécifique nommée &lt;code&gt;__call__&lt;/code&gt;. Si l’objet est appelée comme une
fonction, il retournera la valeur retournée par cette méthode.&lt;/p&gt;
&lt;p&gt;Les deux exemples ci-dessous sont similaires :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# 1er exemple : une fonction
# -------------------------------------------------------------------------
def foo():
    &quot;&quot;&quot;
    Callable affichant la chaîne de caractères &quot;Hello&quot;.
    &quot;&quot;&quot;
    print u&quot;Hello&quot;

# Ici, on appelle directement la fonction
foo()

# 2ème exemple : une classe avec méthode &quot;__call__&quot;
# -------------------------------------------------------------------------
class Foo(object):
    &quot;&quot;&quot;
    Callable affichant la chaîne de caractères &quot;Hello&quot;.
    &quot;&quot;&quot;

    def __call__(self):
        print u&quot;Hello&quot;

# Ici, on instancie la classe et on appelle l&#39;objet comme une fonction
foo = Foo()
foo()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grâce à cette fonctionnalité, il est alors tout à fait possible d’utiliser les
instances d’une classe comme des fonctions. À la seule différence qu’une
instance de classe préserve l’état de ses variables (ce qui permet de modifier
ses données à tout moment) et permet d’effectuer d’autres opérations (via
différentes méthodes), pas seulement un appel à &lt;code&gt;__call__&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
class Add(object):
    &quot;&quot;&quot;
    Callable stockant un compteur que nous pouvons incrémenter à chaque
    appel en lui donnant comme argument un entier.
    &quot;&quot;&quot;

    def __init__(self):
        &quot;&quot;&quot;
        Initialisation du compteur.
        &quot;&quot;&quot;
        self.count = 0

    def __call__(self, value=None):
        &quot;&quot;&quot;
        Méthode exécutée si l&#39;objet est appelé comme une fonction.
        &quot;&quot;&quot;
        if value is not None:
            self.count += int(value)
        return self.count

    def reset(self):
        &quot;&quot;&quot;
        Remet à zéro le compteur.
        &quot;&quot;&quot;
        self.count = 0

add = Add()

print add()
print add(10)
print add()
print add(15)
add.reset()
print add()
print add(2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si on exécute ce code :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0
10
10
25
0
2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans cet exemple, on crée une instance dont le compteur est initialisé à zéro.
Si on appelle cette instance comme une fonction, et si on ne lui donne pas
d’argument, elle renvoie l’état actuel du compteur. Si on lui donne un entier,
elle incrémente son état. L’état est préservé. On peut également effectuer
d’autres opérations sur l’objet, dont notamment la remise à zéro du compteur.&lt;/p&gt;
&lt;p&gt;L’utilisation de &lt;code&gt;__call__&lt;/code&gt; est, notamment, très intéressante avec le framework
web &lt;a href=&quot;http://djangoproject.com&quot;&gt;Django&lt;/a&gt;. &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/http/urls/&quot;&gt;Son système d’URL&lt;/a&gt;
accepte n’importe quel &lt;em&gt;callable&lt;/em&gt;, pas seulement des fonctions. Il est alors
possible d’implémenter des vues reposant sur des classes. On appelle ce type de
vue : &lt;em&gt;class-based&lt;/em&gt;. Les applications &lt;a href=&quot;http://code.djangoproject.com/browser/django/trunk/django/contrib/admin&quot;&gt;admin&lt;/a&gt;
et &lt;a href=&quot;http://code.djangoproject.com/browser/django/trunk/django/contrib/syndication&quot;&gt;syndication&lt;/a&gt;
utilisent cette technique. D’autres exemples ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le projet &lt;a href=&quot;http://github.com/bfirsh/django-class-based-views&quot;&gt;django-class-based-views&lt;/a&gt; de &lt;a href=&quot;http://benfirshman.com/&quot;&gt;Ben Fishman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Le projet &lt;a href=&quot;http://github.com/zacharyvoase/django-clsview&quot;&gt;django-clsview&lt;/a&gt; de &lt;a href=&quot;http://zacharyvoase.com/&quot;&gt;Zachary Voase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;La branche &lt;a href=&quot;http://github.com/jacobian/django/tree/class-based-generic-views/django/views/generic2/&quot;&gt;class-based-generic-views&lt;/a&gt; de &lt;a href=&quot;http://jacobian.org&quot;&gt;Jacob Kaplan-Moss&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.slideshare.net/simon/classbased-views-with-django&quot;&gt;Les slides&lt;/a&gt; de la conférence de &lt;a href=&quot;http://simonwillison.net&quot;&gt;Simon Willison&lt;/a&gt; sur le sujet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour savoir si un objet est &lt;em&gt;callable&lt;/em&gt; ou pas, il est tentant d’utiliser
la fonction native &lt;code&gt;callable&lt;/code&gt;, incluse dans Python 2.x. Elle prend pour
argument l’objet à tester. Si elle renvoie &lt;code&gt;True&lt;/code&gt;, l’objet est bien &lt;em&gt;callable&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Exemple avec une fonction et un entier :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-pycon&quot;&gt;&gt;&gt;&gt; def foo():
...     pass
...
&gt;&gt;&gt; callable(foo)
True
&gt;&gt;&gt; i = 3
&gt;&gt;&gt; callable(i)
False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mais cette fonction a été supprimée dans la version 3 du langage. On peut,
cependant, aisément la remplacer par une fonction &lt;em&gt;lambda&lt;/em&gt; qui vérifie si
l’objet passé en argument possède ou non un attribue nommé &lt;code&gt;__call__&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-pycon&quot;&gt;&gt;&gt;&gt; callable = lambda o: hasattr(o, &#39;__call__&#39;)
&gt;&gt;&gt; callable(foo)
True
&gt;&gt;&gt; callable(i)
False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En revanche, si les classes &lt;em&gt;new-style&lt;/em&gt; héritant d’&lt;em&gt;object&lt;/em&gt; disposent
par défaut d’une méthode &lt;code&gt;__call__&lt;/code&gt;, les classes &lt;em&gt;old-style&lt;/em&gt; (ou &lt;em&gt;classic&lt;/em&gt;)
n’héritant pas d’&lt;em&gt;object&lt;/em&gt; n’ont pas de méthode &lt;code&gt;__call__&lt;/code&gt; mais sont considérées
comme &lt;em&gt;callable&lt;/em&gt; (puisqu’elles retournent une instance). C’est un point
important puisque notre fonction &lt;em&gt;lambda&lt;/em&gt; appliquée sur une classe &lt;em&gt;old-style&lt;/em&gt;
renverra systématiquement &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Exemple :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-pycon&quot;&gt;&gt;&gt;&gt; is_callable = lambda o: hasattr(o, &#39;__call__&#39;)
&gt;&gt;&gt; class Foo:
...     pass
...
...
&gt;&gt;&gt; callable(Foo)
True
&gt;&gt;&gt; is_callable(Foo)
False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On remarque bien dans cet exemple que notre fonction &lt;em&gt;lambda&lt;/em&gt; considère notre
classe &lt;em&gt;old-style&lt;/em&gt; comme &lt;em&gt;non-callable&lt;/em&gt;, alors que la fonction native
&lt;code&gt;callable&lt;/code&gt; la considère comme &lt;em&gt;callable&lt;/em&gt;. Méfiance. Cela peut être source de
bogue. Le plus sage serait de bannir définitivement les classes &lt;em&gt;old-style&lt;/em&gt;
puisqu’elles ont disparu dans la version 3 du langage. Ou de continuer à
utiliser la fonction &lt;code&gt;callable&lt;/code&gt; (ou votre propre implémentation), si vous
utilisez des classes &lt;em&gt;old-style&lt;/em&gt; avec une version 2.x.&lt;/p&gt;
&lt;p&gt;Précision. Si une classe &lt;em&gt;new-style&lt;/em&gt; est &lt;em&gt;callable&lt;/em&gt; par défaut, on parle bien
de la “classe” et non de ses éventuelles instances. Si on instancie un objet à
partir de cette classe, il ne possédera pas de méthode &lt;code&gt;__call__&lt;/code&gt;, sauf si nous
la définissons explicitement.&lt;/p&gt;
&lt;p&gt;Preuve à l’appui :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-pycon&quot;&gt;&gt;&gt;&gt; class Bar(object):
...     pass
...
&gt;&gt;&gt; hasattr(Bar, &#39;__call__&#39;)
True
&gt;&gt;&gt; bar = Bar()
&gt;&gt;&gt; hasattr(bar, &#39;__call__&#39;)
False
&gt;&gt;&gt; class Bar(object):
...     def __call__(self):
...         pass
...
&gt;&gt;&gt; bar = Bar()
&gt;&gt;&gt; hasattr(bar, &#39;__call__&#39;)
True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maintenant qu’on s’est familiarisé avec les &lt;em&gt;callables&lt;/em&gt;, faisons quelques tests.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-

class OldStyle:
    foo = &quot;attribut foo&quot;

    def bar(self):
        pass

class OldStyleCall:
    foo = &quot;attribut foo&quot;
    def __call__(self):
        pass

    def bar(self):
        pass

class NewStyle(object):
    foo = &quot;attribut foo&quot;

    def bar(self):
        pass

class NewStyleCall(object):
    foo = &quot;attribut foo&quot;

    def __call__(self):
        pass

    def bar(self):
        pass

old_style = OldStyle()
old_style_call = OldStyleCall()
new_style = NewStyle()
new_style_call = NewStyleCall()

generator_expr = (2*x for x in (1, 2, 3))

def generator_func(n):
    while n &gt; 0:
        yield n
        n -= 1

def function():
    pass

lambda_func = lambda x: x

TEST_OBJECTS = (
    (&#39;sep&#39;, u&#39;Types natifs&#39;),
    (u&quot;Entier&quot;,  10),
    (u&quot;Flottant&quot;, 10.9),
    (u&quot;Chaîne de caractères&quot;, u&quot;Hello&quot;),
    (u&quot;Booléen&quot;, True),
    (u&quot;Liste&quot;, [1, 2, 3]),
    (u&quot;Tuple&quot;, (1, 2, 3)),
    (u&quot;Dictionnaire&quot;, {u&quot;1&quot;: 1, u&quot;2&quot;: 2, u&quot;3&quot;: 3}),

    (&#39;sep&#39;, u&#39;Generators&#39;),
    (u&quot;Expression&quot;, generator_expr),
    (u&quot;Fonction&quot;, generator_func),

    (&#39;sep&#39;, u&#39;Fonctions&#39;),
    (u&quot;Fonction&quot;, function),
    (u&quot;Fonction lambda&quot;, lambda_func),

    (&#39;sep&#39;, u&#39;Classes &quot;Old-Style&quot;&#39;),
    (u&quot;Classe&quot;, OldStyle),
    (u&quot;Méthode de classe&quot;, OldStyle.bar),
    (u&quot;Attribut de classe&quot;, OldStyle.foo),

    (&#39;sep&#39;, u&#39;Classes &quot;Old-Style&quot; avec méthode &quot;__call__&quot;&#39;),
    (u&quot;Classe&quot;, OldStyleCall),
    (u&quot;Méthode de classe&quot;, OldStyleCall.bar),
    (u&quot;Attribut de classe&quot;, OldStyleCall.foo),

    (&#39;sep&#39;, u&#39;Classes &quot;New-Style&quot;&#39;),
    (u&quot;Classe&quot;, NewStyle),
    (u&quot;Méthode de classe&quot;, NewStyle.bar),
    (u&quot;Attribut de classe&quot;, NewStyle.foo),

    (&#39;sep&#39;, u&#39;Classes &quot;New-Style&quot; avec méthode &quot;__call__&quot;&#39;),
    (u&quot;Classe&quot;, NewStyleCall),
    (u&quot;Méthode de classe&quot;, NewStyleCall.bar),
    (u&quot;Attribut de classe&quot;, NewStyleCall.foo),

    (&#39;sep&#39;, u&#39;Instance de classe &quot;Old-Style&quot;&#39;),
    (u&quot;Instance&quot;, old_style),
    (u&quot;Méthode d&#39;instance&quot;, old_style.bar),
    (u&quot;Attribut d&#39;instance&quot;, old_style.foo),

    (&#39;sep&#39;, u&#39;Instance de classe &quot;Old-Style&quot; avec méthode &quot;__call__&quot;&#39;),
    (u&quot;Instance&quot;, old_style_call),
    (u&quot;Méthode d&#39;instance&quot;, old_style_call.bar),
    (u&quot;Attribut d&#39;instance&quot;, old_style_call.foo),

    (&#39;sep&#39;, u&#39;Instance de classe &quot;New-Style&quot;&#39;),
    (u&quot;Instance&quot;, new_style),
    (u&quot;Méthode d&#39;instance&quot;, new_style.bar),
    (u&quot;Attribut d&#39;instance&quot;, new_style.foo),

    (&#39;sep&#39;, u&#39;Instance de classe &quot;New-Style&quot; avec méthode &quot;__call__&quot;&#39;),
    (u&quot;Instance&quot;, new_style_call),
    (u&quot;Méthode d&#39;instance&quot;, new_style_call.bar),
    (u&quot;Attribut d&#39;instance&quot;, new_style_call.foo),
    )

is_callable = lambda o: u&quot;OUI&quot; if callable(o) else u&quot;NON&quot;

for k, v in TEST_OBJECTS:
    if k == &#39;sep&#39;:
        print &quot;&quot;
        print v
        print &quot;-&quot; * 54
    else:
        print u&quot;%s : %s&quot; % (k, is_callable(v))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vérifions :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Types natifs
------------------------------------------------------
Entier : NON
Flottant : NON
Chaîne de caractères : NON
Booléen : NON
Liste : NON
Tuple : NON
Dictionnaire : NON

Generators
------------------------------------------------------
Expression : NON
Fonction : OUI

Fonctions
------------------------------------------------------
Fonction : OUI
Fonction lambda : OUI

Classes &quot;Old-Style&quot;
------------------------------------------------------
Classe : OUI
Méthode de classe : OUI
Attribut de classe : NON

Classes &quot;Old-Style&quot; avec méthode &quot;__call__&quot;
------------------------------------------------------
Classe : OUI
Méthode de classe : OUI
Attribut de classe : NON

Classes &quot;New-Style&quot;
------------------------------------------------------
Classe : OUI
Méthode de classe : OUI
Attribut de classe : NON

Classes &quot;New-Style&quot; avec méthode &quot;__call__&quot;
------------------------------------------------------
Classe : OUI
Méthode de classe : OUI
Attribut de classe : NON

Instance de classe &quot;Old-Style&quot;
------------------------------------------------------
Instance : NON
Méthode d&#39;instance : OUI
Attribut d&#39;instance : NON

Instance de classe &quot;Old-Style&quot; avec méthode &quot;__call__&quot;
------------------------------------------------------
Instance : OUI
Méthode d&#39;instance : OUI
Attribut d&#39;instance : NON

Instance de classe &quot;New-Style&quot;
------------------------------------------------------
Instance : NON
Méthode d&#39;instance : OUI
Attribut d&#39;instance : NON

Instance de classe &quot;New-Style&quot; avec méthode &quot;__call__&quot;
------------------------------------------------------
Instance : OUI
Méthode d&#39;instance : OUI
Attribut d&#39;instance : NON
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finalement, rien de bien compliqué.&lt;/p&gt;

    </content>
    
    <category term="python" />
    
    <category term="development" />
    
    <category term="django" />
    
  </entry>
  
  <entry xml:lang="fr">
    <id>http://gillesfabio.com/blog/2010/07/22/creer-un-blog-avec-django-1-2</id>
    <link href="http://gillesfabio.com/blog/2010/07/22/creer-un-blog-avec-django-1-2" rel="alternate" type="text/html" />
    <title>Créer un blog avec Django 1.2</title>
    <published>2010-07-22T00:00:00.000Z</published>
    <updated>2010-07-22T00:00:00.000Z</updated>
    <author>
      <name>Gilles Fabio</name>
    </author>
    <content type="html">
      &lt;p&gt;&lt;strong&gt;Cet article est une mise à jour d’une précédente publication :
&lt;a href=&quot;http://gillesfabio.com/blog/2009/01/29/creer-un-blog-avec-django/&quot;&gt;créer un blog avec Django&lt;/a&gt;,
basée sur la version 1.1 du framework. La version 1.2 requière de légères
modifications.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Créer un blog avec &lt;a href=&quot;http://www.djangoproject.com&quot;&gt;Django&lt;/a&gt;, &lt;strong&gt;c’est simple&lt;/strong&gt;.
Oui, vraiment. Ce tutorial s’adresse aux personnes connaissant déjà un peu le
langage de programmation &lt;a href=&quot;http://www.python.org&quot;&gt;Python&lt;/a&gt; et disposant d’un
environnement de développement Django opérationnel.&lt;/p&gt;
&lt;p&gt;Brièvement, cela se résume à :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python (interpréteur, libraires… )&lt;/li&gt;
&lt;li&gt;Un gestionnaire de bases de données et son &lt;em&gt;binding&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Un terminal (ou “console”)&lt;/li&gt;
&lt;li&gt;Un navigateur web&lt;/li&gt;
&lt;li&gt;Un éditeur de texte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si vous désirez installer Django sur votre machine, n’hésitez pas à consulter
la section &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/intro/install/#intro-install&quot;&gt;Quick install guide&lt;/a&gt;
de la documentation.&lt;/p&gt;
&lt;h2 id=&quot;mise-en-place-du-projet&quot;&gt;Mise en place du projet&lt;/h2&gt;
&lt;p&gt;Dans ce tutoriel, nous allons utiliser &lt;a href=&quot;http://www.mysql.org&quot;&gt;MySQL&lt;/a&gt; comme
gestionnaire de bases de données mais vous pouvez utiliser n’importe quel autre
gestionnaire de bases de données supporté par Django.&lt;/p&gt;
&lt;p&gt;Il faut donc, tout d’abord, créer une base de données :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mysql -u user -p
mysql&gt; CREATE DATABASE blog;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remplacez &lt;code&gt;user&lt;/code&gt; par l’utilisateur qui va bien (root ou un utilisateur ayant
les droits de création de base). Comme il vaut mieux éviter d’utiliser
l’utilisateur root, même en local, nous allons créer un utilisateur spécifique
pour la base et lui donner les permissions nécessaires. Nous allons nommer cet
utilisateur &lt;code&gt;blog&lt;/code&gt;, du même nom que la base :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql&gt; GRANT ALL ON blog.* TO blog@localhost IDENTIFIED BY &#39;password&#39;;
mysql&gt; FLUSH PRIVILEGES;
mysql&gt; \q
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bien sûr, remplacez &lt;code&gt;password&lt;/code&gt; par un vrai mot de passe.&lt;/p&gt;
&lt;p&gt;Notre base est créée.&lt;/p&gt;
&lt;p&gt;Dans un dossier de votre répertoire personnel (si possible, dans un dossier
dédié à vos projets de programmation), nous allons créer le projet Django
&lt;code&gt;website&lt;/code&gt; et l’application &lt;code&gt;blog&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ django-admin startproject website
$ cd website
$ django-admin startapp blog
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans le répertoire du projet &lt;code&gt;website&lt;/code&gt;, nous allons créer un dossier
&lt;code&gt;templates&lt;/code&gt; (qui contiendra nos templates) et un dossier &lt;code&gt;media&lt;/code&gt; qui
contiendra les fichiers statiques :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir templates
$ mkdir media
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour bien séparer nos applications, nous allons les placer dans un répertoire
&lt;code&gt;apps&lt;/code&gt; à la racine du projet :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir apps
$ touch apps/__init__.py
$ mv blog apps/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;La commande &lt;code&gt;tree&lt;/code&gt; devrait retourner cette arborescence :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
|-- __init__.py
|-- apps
|   |-- __init__.py
|   `-- blog
|       |-- __init__.py
|       |-- models.py
|       `-- views.py
|-- manage.py
|-- media
|-- settings.py
|-- templates
`-- urls.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le projet est en place. Exécutez la commande suivante :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans votre navigateur, pointez l’adresse : &lt;a href=&quot;http://127.0.0.1:8000&quot;&gt;http://127.0.0.1:8000&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bienvenue sous Django ! Control + C pour stopper le serveur.&lt;/p&gt;
&lt;h2 id=&quot;param-tres-et-urls&quot;&gt;Paramètres et URLs&lt;/h2&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;settings.py&lt;/code&gt; à l’aide de votre éditeur favori.&lt;/p&gt;
&lt;p&gt;Tout d’abord, on crée la constante &lt;code&gt;PROJECT_PATH&lt;/code&gt; (en haut du fichier) afin
de stocker le chemin absolu vers notre projet :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;import os.path
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C’est pratique si vous utilisez différents systèmes d’exploitation ou
différentes machines. Le chemin est automatiquement détecté.&lt;/p&gt;
&lt;p&gt;Ensuite, on passe à la base de données :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;DATABASES = {
    &#39;default&#39;: {
        &#39;ENGINE&#39;: &#39;django.db.backends.mysql&#39;,
        &#39;NAME&#39;: &#39;blog&#39;,
        &#39;USER&#39;: &#39;blog&#39;,
        &#39;PASSWORD&#39;: &#39;mot-de-passe&#39;,
        &#39;HOST&#39;: &#39;&#39;,
        &#39;PORT&#39;: &#39;&#39;,
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajuste la &lt;em&gt;timezone&lt;/em&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;TIME_ZONE = &#39;Europe/Paris&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajuste la langue par défaut :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;LANGUAGE_CODE = &#39;fr-fr&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute notre répertoire &lt;code&gt;media&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;MEDIA_ROOT = os.path.join(PROJECT_PATH, &#39;media/&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute l’URL vers les médias :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;MEDIA_URL = &#39;/media/&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajuste l’URL vers les médias de l’interface d’administration :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;ADMIN_MEDIA_PREFIX = &#39;/media/admin/&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute notre répertoire &lt;code&gt;templates&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;TEMPLATE_DIRS = (
    os.path.join(PROJECT_PATH, &#39;templates&#39;),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute notre application &lt;code&gt;blog&lt;/code&gt; à la liste &lt;code&gt;INSTALLED_APPS&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;INSTALLED_APPS = (
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.sites&#39;,
    &#39;django.contrib.messages&#39;,
    &#39;django.contrib.admin&#39;,
    &#39;website.apps.blog&#39;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On enregistre les modifications et on passe aux URLs.&lt;/p&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;urls.py&lt;/code&gt;. Nous allons ajouter le support des médias. Django
va donc prendre en charge les fichiers statiques (pratique quand on développe
en local mais à proscrire en production). Pour ce faire, on importe le module
&lt;code&gt;settings&lt;/code&gt; pour récupérer &lt;code&gt;MEDIA_ROOT&lt;/code&gt; (le chemin absolu vers le répertoire
&lt;code&gt;media&lt;/code&gt;) et on ajoute un &lt;em&gt;urlpatterns&lt;/em&gt; pour &lt;code&gt;django.views.static.serve&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;from django.conf.urls.defaults import *
from django.conf import settings

# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()


urlpatterns = patterns(&#39;&#39;,
    # Example:
    # (r&#39;^website/&#39;, include(&#39;website.foo.urls&#39;)),

    # Uncomment the admin/doc line below and add &#39;django.contrib.admindocs&#39;
    # to INSTALLED_APPS to enable admin documentation:
    # (r&#39;^admin/doc/&#39;, include(&#39;django.contrib.admindocs.urls&#39;)),

    # Uncomment the next line to enable the admin:
    # (r&#39;^admin/(.*)&#39;, include(admin.site.root)),
)

urlpatterns += patterns(&#39;&#39;,
    (r&#39;^media/(?P&lt;path&gt;.*)$&#39;,
        &#39;django.views.static.serve&#39;, {
            &#39;document_root&#39;: settings.MEDIA_ROOT,
            &#39;show_indexes&#39;: True,
        },
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On enregistre les modifications et on passe à l’installation de l’interface
d’administration.&lt;/p&gt;
&lt;h2 id=&quot;installation-de-l-interface-d-administration&quot;&gt;Installation de l’interface d’administration&lt;/h2&gt;
&lt;p&gt;Django embarque une interface d’administration sympathique et pratique.
L’installation se fait en trois étapes : ajout de l’application dans le fichier
&lt;code&gt;settings.py&lt;/code&gt;, ajout des URLs et synchronisation de la base de données.&lt;/p&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;settings.py&lt;/code&gt; et ajoutez &lt;code&gt;django.contrib.admin&lt;/code&gt; dans la liste
&lt;code&gt;INSTALLED_APPS&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;INSTALLED_APPS = (
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.sites&#39;,
    &#39;django.contrib.admin&#39;,
    &#39;website.apps.blog&#39;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enregistrez les modifications.&lt;/p&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;urls.py&lt;/code&gt; et ajoutez le support de l’admin en décommentant les
lignes indiquées dans les commentaires, soit trois lignes au total :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;from django.conf.urls.defaults import *
from django.conf import settings

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()


urlpatterns = patterns(&#39;&#39;,
    # Example:
    # (r&#39;^website/&#39;, include(&#39;website.foo.urls&#39;)),

    # Uncomment the admin/doc line below and add &#39;django.contrib.admindocs&#39;
    # to INSTALLED_APPS to enable admin documentation:
    # (r&#39;^admin/doc/&#39;, include(&#39;django.contrib.admindocs.urls&#39;)),

    # Uncomment the next line to enable the admin:
    (r&#39;^admin/(.*)&#39;, include(admin.site.root)),
)

urlpatterns += patterns(&#39;&#39;,
    (r&#39;^media/(?P&lt;path&gt;.*)$&#39;,
        &#39;django.views.static.serve&#39;, {
            &#39;document_root&#39;: settings.MEDIA_ROOT,
            &#39;show_indexes&#39;: True,
        },
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enregistrez les modifications.&lt;/p&gt;
&lt;p&gt;Il ne reste plus qu’à synchroniser avec la base de données (à exécuter à la
racine du projet) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py syncdb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Django vous guidera dans la création d’un compte super-utilisateur.&lt;/p&gt;
&lt;p&gt;Lancez le serveur :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans votre navigateur, pointer l’adresse : &lt;a href=&quot;http://127.0.0.1:8000/admin/&quot;&gt;http://127.0.0.1:8000/admin/&lt;/a&gt;. Entrez
votre identifiant et votre mot de passe super-utilisateur.  Bienvenue dans
l’interface d’administration de Django !&lt;/p&gt;
&lt;h2 id=&quot;-criture-des-tests&quot;&gt;Écriture des tests&lt;/h2&gt;
&lt;p&gt;Oui, écrire les tests avant le code, c’est mieux. Ça permet d’éviter des bogues
et des prises de tête. Le but est le suivant : faire en sorte que tous les tests
passent. Prêt ? Alors créez un fichier &lt;code&gt;tests.py&lt;/code&gt; à la racine de l’application
&lt;code&gt;blog&lt;/code&gt;. On commence par importer la classe &lt;code&gt;TestCase&lt;/code&gt; du module &lt;code&gt;django.test&lt;/code&gt;
et on crée une classe &lt;code&gt;BlogTest&lt;/code&gt; qui contiendra nos tests :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
from django.test import TestCase


class BlogTest(TestCase):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On lance les tests (à exécuter à la racine du projet) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py test
Creating test database...
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log
Installing index for auth.Permission model
Installing index for auth.Message model
Installing index for admin.LogEntry model
................
----------------------------------------------------------------------
Ran 16 tests in 2.134s

OK
Destroying test database...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tous les tests passent ! Normal, nous n’en avons écrit aucun. Au boulot !&lt;/p&gt;
&lt;p&gt;Notre classe de test :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
from django.test import TestCase
from django.core.urlresolvers import reverse


class BlogTest(TestCase):
    &quot;&quot;&quot;
    Tests of ``blog`` application.
    &quot;&quot;&quot;
    fixtures = [&#39;test_data&#39;]

    def test_entry_archive_index(self):
        &quot;&quot;&quot;
        Tests ``entry_archive`` view.

        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog&#39;))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive.html&#39;)

    def test_entry_archive_year(self):
        &quot;&quot;&quot;
        Tests ``entry_archive_year`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_year&#39;, args=[&#39;2010&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive_year.html&#39;)

    def test_entry_archive_month(self):
        &quot;&quot;&quot;
        Tests ``entry_archive_month``view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_month&#39;, args=[&#39;2010&#39;, &#39;07&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive_month.html&#39;)

    def test_entry_archive_day(self):
        &quot;&quot;&quot;
        Tests ``entry_archive_day`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_day&#39;, args=[&#39;2010&#39;, &#39;07&#39;, &#39;21&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive_day.html&#39;)

    def test_entry_detail(self):
        &quot;&quot;&quot;
        Tests ``entry_detail`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_entry&#39;, args=[&#39;2010&#39;, &#39;07&#39;, &#39;21&#39;, &#39;test-entry&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_detail.html&#39;)

    def test_entry_detail_not_found(self):
        &quot;&quot;&quot;
        Test ``entry_detail`` view with an offline entry.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_entry&#39;, args=[&#39;2010&#39;, &#39;07&#39;, &#39;21&#39;, &#39;offline-entry&#39;]))
        self.failUnlessEqual(response.status_code, 404)

    def test_category_detail(self):
        &quot;&quot;&quot;
        Tests ``category_detail`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_category&#39;, args=[&#39;test&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/category_detail.html&#39;)

    def test_category_detail_not_found(self):
        &quot;&quot;&quot;
        Tests ``category_detail`` view with an offline category.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_category&#39;, args=[&#39;offline&#39;]))
        self.failUnlessEqual(response.status_code, 404)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La fonction &lt;code&gt;reverse&lt;/code&gt; est utilisée pour récupérer l’URL en fonction de son
nom (URLs nommées). Pour en savoir plus, n’hésitez pas à consulter la section
&lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/http/urls/&quot;&gt;URL dispatcher&lt;/a&gt; de la
documentation. Nous testons ici la réponse et le template (pour vérifier que la
future vue renverra bien le bon template). Si vous relancez les tests, vous
devriez vous faire insulter. C’est normal. Nous n’avons encore rien implémenté.
Donc, passons à l’implémentation.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-mod-les&quot;&gt;Création des modèles&lt;/h2&gt;
&lt;p&gt;Nous allons réaliser un blog “basique” composé de deux modèles : &lt;code&gt;Entry&lt;/code&gt; et
&lt;code&gt;Category&lt;/code&gt;. Le premier modèle représente un billet de blog et le deuxième une
catégorie pour classer les billets par thème.&lt;/p&gt;
&lt;p&gt;Un billet est composé des champs suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un titre&lt;/li&gt;
&lt;li&gt;Un slug (aussi appelé “permalien”)&lt;/li&gt;
&lt;li&gt;Un auteur&lt;/li&gt;
&lt;li&gt;Une catégorie&lt;/li&gt;
&lt;li&gt;Une date de création&lt;/li&gt;
&lt;li&gt;Une date de modification&lt;/li&gt;
&lt;li&gt;Une date de publication&lt;/li&gt;
&lt;li&gt;Un statut (en ligne / hors ligne)&lt;/li&gt;
&lt;li&gt;Un corps au format HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une categorie est composée des champs suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un nom&lt;/li&gt;
&lt;li&gt;Un slug (aussi appelé “permalien”)&lt;/li&gt;
&lt;li&gt;Une date de création&lt;/li&gt;
&lt;li&gt;Une date de modification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;models.py&lt;/code&gt; du répertoire &lt;code&gt;blog&lt;/code&gt;. Ce fichier contiendra les
modèles de notre application. Pour en savoir plus, n’hésitez pas à consulter
la section &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/db/models/#topics-db-models&quot;&gt;Writing models&lt;/a&gt;
de la documentation.&lt;/p&gt;
&lt;p&gt;Nos modèles :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Models of ``blog`` application.
&quot;&quot;&quot;
from datetime import datetime

from django.db import models
from django.utils.translation import ugettext_lazy as _


class Category(models.Model):
    &quot;&quot;&quot;
    A blog category.
    &quot;&quot;&quot;
    name = models.CharField(_(&#39;name&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique=True)
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)

    class Meta:
        verbose_name = _(&#39;category&#39;)
        verbose_name_plural = _(&#39;categories&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.name

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_category&#39;, (), {
            &#39;slug&#39;: self.slug,
        })


class Entry(models.Model):
    &quot;&quot;&quot;
    A blog entry.
    &quot;&quot;&quot;
    STATUS_OFFLINE = 0
    STATUS_ONLINE = 1
    STATUS_DEFAULT = STATUS_OFFLINE
    STATUS_CHOICES = (
        (STATUS_OFFLINE, _(&#39;Offline&#39;)),
        (STATUS_ONLINE, _(&#39;Online&#39;)),
    )

    title = models.CharField(_(&#39;title&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique_for_date=&#39;publication_date&#39;)
    author = models.ForeignKey(&#39;auth.User&#39;, verbose_name=_(&#39;author&#39;))
    category = models.ForeignKey(Category, verbose_name=_(&#39;category&#39;))
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)
    publication_date = models.DateTimeField(_(&#39;publication date&#39;), default=datetime.now(), db_index=True)
    status = models.IntegerField(_(&#39;status&#39;), choices=STATUS_CHOICES, default=STATUS_DEFAULT, db_index=True)
    body = models.TextField(_(&#39;body&#39;))

    class Meta:
        verbose_name = _(&#39;entry&#39;)
        verbose_name_plural = _(&#39;entries&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.title

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_entry&#39;, (), {
            &#39;year&#39;: self.publication_date.strftime(&#39;%Y&#39;),
            &#39;month&#39;: self.publication_date.strftime(&#39;%m&#39;),
            &#39;day&#39;: self.publication_date.strftime(&#39;%d&#39;),
            &#39;slug&#39;: self.slug,
        })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La syntaxe du langage Python est tellement &lt;em&gt;clean&lt;/em&gt; que le code parle de lui-même.&lt;/p&gt;
&lt;p&gt;Nos modèles sont &lt;em&gt;i18n-ready&lt;/em&gt; (via la fonction magique &lt;code&gt;_()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Avant de créer nos tables, il est recommandé de vérifier si les modèles ne
comportent aucune erreur. Pour ce faire, à la racine du projet, on exécute
la commande suivante :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py validate
0 errors found
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si cette commande renvoie des erreurs, il suffira de les corriger.&lt;/p&gt;
&lt;p&gt;Tout est OK. On synchronise avec la base de données:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py syncdb
Creating table blog_category
Creating table blog_entry
Installing index for blog.Entry model
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Par la suite, dans nos templates, nous afficheront uniquement les billets ayant
pour statut “en ligne”. Lors de la récupération de nos objets, on peut très bien
filtrer sur ce champ. Mais parce qu’on est feignant, on va créer des &lt;em&gt;managers&lt;/em&gt;
pour s’épargner du code.&lt;/p&gt;
&lt;p&gt;Les méthodes d’un manager s’appliquent à une table, tandis que les méthodes d’un
modèle s’appliquent à un objet. Donc, si nous voulons récupérer tous les billets
ayant pour statut “en ligne”, nous avons besoin d’un manager. Si nous voulons
récupérer le nom complet de l’auteur du billet, nous devons définir une méthode
spécifique dans le modèle.&lt;/p&gt;
&lt;p&gt;Nous avons besoin de deux managers : un pour manipuler uniquement les billets
“en ligne” et un autre pour manipuler uniquement les catégories ayant des billets
“en ligne” (c’est-à-dire que si nous rédigeons un seul billet dans une catégorie et
que ce billet est “hors ligne”, la catégorie ne doit pas exister publiquement).&lt;/p&gt;
&lt;p&gt;Dans le répertoire de notre application, on crée un fichier (ou plutôt, un &lt;em&gt;module&lt;/em&gt;)
nommé &lt;code&gt;managers.py&lt;/code&gt;. N’hésitez pas à consulter la section &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/db/managers/&quot;&gt;Managers&lt;/a&gt;
de la documentation pour en savoir plus.&lt;/p&gt;
&lt;p&gt;Nos managers :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Managers of ``blog`` application.
&quot;&quot;&quot;
from django.db import models


class CategoryOnlineManager(models.Manager):
    &quot;&quot;&quot;
    Manager that manages online ``Category`` objects.
    &quot;&quot;&quot;

    def get_query_set(self):
        from website.apps.blog.models import Entry
        entry_status = Entry.STATUS_ONLINE
        return super(CategoryOnlineManager, self).get_query_set().filter(
            entry__status=entry_status).distinct()


class EntryOnlineManager(models.Manager):
    &quot;&quot;&quot;
    Manager that manages online ``Entry`` objects.
    &quot;&quot;&quot;

    def get_query_set(self):
        return super(EntryOnlineManager, self).get_query_set().filter(
            status=self.model.STATUS_ONLINE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il faut maintenant ajouter ces managers dans nos modèles :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Models of ``blog`` application.
&quot;&quot;&quot;
from datetime import datetime

from django.db import models
from django.utils.translation import ugettext_lazy as _

from website.apps.blog.managers import CategoryOnlineManager
from website.apps.blog.managers import EntryOnlineManager


class Category(models.Model):
    &quot;&quot;&quot;
    A blog category.
    &quot;&quot;&quot;
    name = models.CharField(_(&#39;name&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique=True)
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)

    objects = models.Manager()
    online_objects = CategoryOnlineManager()

    class Meta:
        verbose_name = _(&#39;category&#39;)
        verbose_name_plural = _(&#39;categories&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.name

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_category&#39;, (), {
            &#39;slug&#39;: self.slug,
        })


class Entry(models.Model):
    &quot;&quot;&quot;
    A blog entry.
    &quot;&quot;&quot;
    STATUS_OFFLINE = 0
    STATUS_ONLINE = 1
    STATUS_DEFAULT = STATUS_OFFLINE
    STATUS_CHOICES = (
        (STATUS_OFFLINE, _(&#39;Offline&#39;)),
        (STATUS_ONLINE, _(&#39;Online&#39;)),
    )

    title = models.CharField(_(&#39;title&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique_for_date=&#39;publication_date&#39;)
    author = models.ForeignKey(&#39;auth.User&#39;, verbose_name=_(&#39;author&#39;))
    category = models.ForeignKey(Category, verbose_name=_(&#39;category&#39;))
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)
    publication_date = models.DateTimeField(_(&#39;publication date&#39;), default=datetime.now(), db_index=True)
    status = models.IntegerField(_(&#39;status&#39;), choices=STATUS_CHOICES, default=STATUS_DEFAULT, db_index=True)
    body = models.TextField(_(&#39;body&#39;))

    objects = models.Manager()
    online_objects = EntryOnlineManager()

    class Meta:
        verbose_name = _(&#39;entry&#39;)
        verbose_name_plural = _(&#39;entries&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.title

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_entry&#39;, (), {
            &#39;year&#39;: self.publication_date.strftime(&#39;%Y&#39;),
            &#39;month&#39;: self.publication_date.strftime(&#39;%m&#39;),
            &#39;day&#39;: self.publication_date.strftime(&#39;%d&#39;),
            &#39;slug&#39;: self.slug,
        })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nous avons des modèles, des managers, une interface d’administration… Ah tiens,
et si on ajoutait nos modèles dans l’admin ? Il serait peut-être temps de
rédiger quelques billets et de créer quelques catégories pour nos tests.&lt;/p&gt;
&lt;h2 id=&quot;ajout-des-mod-les-dans-l-interface-d-administration&quot;&gt;Ajout des modèles dans l’interface d’administration&lt;/h2&gt;
&lt;p&gt;Pour ajouter nos modèles dans l’interface d’administration, nous devons créer
une classe de type &lt;code&gt;ModelAdmin&lt;/code&gt; par modèle. Chaque classe embarquera des
options et des méthodes propres à l’admin. Par convention, on placera ces
classes dans un module &lt;code&gt;admin.py&lt;/code&gt; dans le répertoire de l’application.
N’hésitez pas à consulter la section &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/ref/contrib/admin/&quot;&gt;The Django admin site&lt;/a&gt;
de la documentation pour en savoir plus.&lt;/p&gt;
&lt;p&gt;Créez le fichier &lt;code&gt;admin.py&lt;/code&gt; dans le répertoire &lt;code&gt;blog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Nos classes admin :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Administration interface options of ``blog`` application.
&quot;&quot;&quot;
from django.contrib import admin

from website.apps.blog.models import Category
from website.apps.blog.models import Entry


class CategoryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Category`` model.
    &quot;&quot;&quot;
    pass


class EntryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Entry`` model.
    &quot;&quot;&quot;
    pass


admin.site.register(Category, CategoryAdmin)
admin.site.register(Entry, EntryAdmin)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour l’instant, on se contente du minimum. Il est possible de presque tout
personnaliser. Nous allons quand même améliorer un peu. Voici une version un
peu plus peaufinée :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Administration interface options of ``blog`` application.
&quot;&quot;&quot;
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _

from website.apps.blog.models import Category
from website.apps.blog.models import Entry


class CategoryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Category`` model.
    &quot;&quot;&quot;
    list_display = (&#39;name&#39;, &#39;slug&#39;, &#39;creation_date&#39;, &#39;modification_date&#39;)
    search_fields = (&#39;name&#39;,)
    date_hierarchy = &#39;creation_date&#39;
    save_on_top = True
    prepopulated_fields = {&#39;slug&#39;: (&#39;name&#39;,)}


class EntryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Entry`` model.
    &quot;&quot;&quot;
    list_display = (&#39;title&#39;, &#39;category&#39;, &#39;status&#39;, &#39;author&#39;)
    search_fields = (&#39;title&#39;, &#39;body&#39;)
    date_hierarchy = &#39;publication_date&#39;
    fieldsets = (
        (_(&#39;Headline&#39;), {&#39;fields&#39;: (&#39;author&#39;, &#39;title&#39;, &#39;slug&#39;, &#39;category&#39;)}),
        (_(&#39;Publication&#39;), {&#39;fields&#39;: (&#39;publication_date&#39;, &#39;status&#39;)}),
        (_(&#39;Body&#39;), {&#39;fields&#39;: (&#39;body&#39;,)}),
    )
    save_on_top = True
    radio_fields = {&#39;status&#39;: admin.VERTICAL}
    prepopulated_fields = {&#39;slug&#39;: (&#39;title&#39;,)}

admin.site.register(Category, CategoryAdmin)
admin.site.register(Entry, EntryAdmin)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maintenant qu’on peut créer des billets et des catégories, nous allons en
profiter pour créer des fixtures pour nos tests. Les fixtures sont des données
de test.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-fixtures&quot;&gt;Création des fixtures&lt;/h2&gt;
&lt;p&gt;On crée deux catégories. La première :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Test&lt;/li&gt;
&lt;li&gt;Slug : test&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La seconde :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Offline&lt;/li&gt;
&lt;li&gt;Slug : offline&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et deux billets. Le premier :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Test Entry&lt;/li&gt;
&lt;li&gt;Slug : test-entry&lt;/li&gt;
&lt;li&gt;Catégorie : Test&lt;/li&gt;
&lt;li&gt;Date de publication : 2010-07-21 00:00:00&lt;/li&gt;
&lt;li&gt;Statut : en ligne&lt;/li&gt;
&lt;li&gt;Corps : peu importe, ce que vous voulez&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le second :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Offline&lt;/li&gt;
&lt;li&gt;Slug : offline-entry&lt;/li&gt;
&lt;li&gt;Catégorie : Offline&lt;/li&gt;
&lt;li&gt;Date de publication : 2010-07-21 00:00:00&lt;/li&gt;
&lt;li&gt;Statut : hors ligne&lt;/li&gt;
&lt;li&gt;Corps : peu importe, ce que vous voulez&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une fois ces données sauvegardées, on va les exporter au format JSON pour
pouvoir les réutiliser automatiquement dans nos tests.&lt;/p&gt;
&lt;p&gt;Créons tout d’abord un répertoire &lt;code&gt;fixtures&lt;/code&gt; dans le répertoire de l’application.&lt;/p&gt;
&lt;p&gt;Puis, exécutez cette commande à la racine du projet :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py dumpdata blog --indent=4 &gt; apps/blog/fixtures/test_data.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nos fixtures sont prêtes. Passons aux URLs.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-urls&quot;&gt;Création des URLs&lt;/h2&gt;
&lt;p&gt;Nous n’avons même pas besoin de créer de vue pour notre application puisque nous
allons utiliser les vues génériques de Django. N’hésitez pas à consulter les
sections &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/ref/generic-views/#ref-generic-views&quot;&gt;Generic Views&lt;/a&gt;
et &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/topics/http/urls/&quot;&gt;URL dispatcher&lt;/a&gt; de la
documentation pour en savoir plus.&lt;/p&gt;
&lt;p&gt;Par convention, les URLs seront contenues dans le module &lt;code&gt;urls.py&lt;/code&gt; dans le
répertoire de l’application (ce fichier n’existe pas, donc pensez à le créer).&lt;/p&gt;
&lt;p&gt;Nos URLs :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
URLs of ``blog`` application.
&quot;&quot;&quot;
from django.conf.urls.defaults import *

from website.apps.blog.models import Entry
from website.apps.blog.models import Category


urlpatterns = patterns(&#39;&#39;,
    url(r&#39;^(?P&lt;year&gt;\d{4})/(?P&lt;month&gt;\d{2})/(?P&lt;day&gt;\d{2})/(?P&lt;slug&gt;[\w-]+)/$&#39;,
        &#39;django.views.generic.date_based.object_detail&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            month_format=&#39;%m&#39;,
            date_field=&#39;publication_date&#39;,
            slug_field=&#39;slug&#39;,
        ),
        name=&#39;blog_entry&#39;,
    ),
    url(r&#39;^(?P&lt;year&gt;\d{4})/(?P&lt;month&gt;\d{2})/(?P&lt;day&gt;\d{2})/$&#39;,
        &#39;django.views.generic.date_based.archive_day&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            month_format=&#39;%m&#39;,
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog_day&#39;,
    ),
    url(r&#39;^(?P&lt;year&gt;\d{4})/(?P&lt;month&gt;\d{2})/$&#39;,
        &#39;django.views.generic.date_based.archive_month&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            month_format=&#39;%m&#39;,
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog_month&#39;,
    ),
    url(r&#39;^(?P&lt;year&gt;\d{4})/$&#39;,
        &#39;django.views.generic.date_based.archive_year&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            make_object_list=True,
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog_year&#39;,
    ),
    url(r&#39;^category/(?P&lt;slug&gt;[\w-]+)/$&#39;,
        &#39;django.views.generic.list_detail.object_detail&#39;,
        dict(
            queryset=Category.online_objects.all(),
            slug_field=&#39;slug&#39;
        ),
        name=&#39;blog_category&#39;,
    ),
    url(r&#39;^$&#39;,
        &#39;django.views.generic.date_based.archive_index&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog&#39;,
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notre projet n’est pas encore au courant de ces URLs. Éditez le fichier &lt;code&gt;urls.py&lt;/code&gt;
à la racine du projet et ajoutez le module via la fonction &lt;code&gt;include&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;from django.conf.urls.defaults import *
from django.conf import settings

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()


urlpatterns = patterns(&#39;&#39;,
    # Example:
    # (r&#39;^website/&#39;, include(&#39;website.foo.urls&#39;)),
    (r&#39;&#39;, include(&#39;website.apps.blog.urls&#39;)),

    # Uncomment the admin/doc line below and add &#39;django.contrib.admindocs&#39;
    # to INSTALLED_APPS to enable admin documentation:
    # (r&#39;^admin/doc/&#39;, include(&#39;django.contrib.admindocs.urls&#39;)),

    # Uncomment the next line to enable the admin:
    (r&#39;^admin/&#39;, include(admin.site.urls)),

)

urlpatterns += patterns(&#39;&#39;,
    (r&#39;^media/(?P&lt;path&gt;.*)$&#39;,
        &#39;django.views.static.serve&#39;, {
            &#39;document_root&#39;: settings.MEDIA_ROOT,
            &#39;show_indexes&#39;: True,
        },
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Relançons nos tests :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ça ne passe toujours pas mais vous avez certainement remarqué que les erreurs
sont différentes. Vous ne devriez voir que des erreurs de templates. Il y a
donc une progression !&lt;/p&gt;
&lt;p&gt;Passons à la création des templates.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-templates&quot;&gt;Création des templates&lt;/h2&gt;
&lt;p&gt;Nous allons, dans un premier temps, créer uniquement des templates vides. Puis,
nous relancerons nos tests pour vérifier si ils passent bien à présent. Il
restera alors juste à remplir les templates pour afficher les données.&lt;/p&gt;
&lt;p&gt;On se place à la racine du projet et on crée les fichiers :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir templates/layout
$ mkdir templates/blog
$ touch templates/layout/base.html
$ touch templates/blog/entry_detail.html
$ touch templates/blog/category_detail.html
$ touch templates/blog/entry_archive.html
$ touch templates/blog/entry_archive_year.html
$ touch templates/blog/entry_archive_month.html
$ touch templates/blog/entry_archive_day.html
$ touch templates/404.html
$ touch templates/500.html
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Relançons les tests :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nos tests passent ! Nous devons maintenant remplir ces templates.&lt;/p&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/layout/base.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;&lt;!DOCTYPE html PUBLIC &#39;-//W3C//DTD XHTML 1.0 Strict//EN&#39; &#39;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&#39;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;fr&quot; lang=&quot;fr&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;{% block title %}{% endblock title %} - Django Blog&lt;/title&gt;
        &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;
        &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;all&quot; href=&quot;{{ MEDIA_URL }}css/screen.css&quot; /&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id=&quot;header&quot;&gt;
            &lt;h1&gt;&lt;a href=&quot;{% url blog %}&quot;&gt;Django Blog&lt;/a&gt;&lt;/h1&gt;
        &lt;/div&gt;
        &lt;div id=&quot;content&quot;&gt;
            {% block content %}{% endblock content %}
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/404.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% load i18n %}
{% block title %}{% trans &quot;404 Not Found&quot; %}{% endblock title %}
{% block content %}
    &lt;p&gt;&lt;strong&gt;{% trans &quot;404 Not Found&quot; %}&lt;/strong&gt;&lt;/p&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/500.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% load i18n %}
{% block title %}{% trans &quot;error 500&quot; %}{% endblock title %}
{% block content %}
    &lt;p&gt;&lt;strong&gt;{% trans &quot;Error 500&quot; %}&lt;/strong&gt;&lt;/p&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% load i18n %}
{% block title %}{% trans &quot;Latest entries&quot; %}{% endblock title %}
{% block content %}
&lt;h2&gt;{% trans &quot;Latest entries&quot; %}&lt;/h2&gt;
{% if latest %}
    {% for entry in latest %}
    &lt;div class=&quot;entry&quot;&gt;
        &lt;h3&gt;&lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt;&lt;/h3&gt;
        &lt;p class=&quot;entry-meta&quot;&gt;
            {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
            &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/p&gt;
        &lt;div class=&quot;entry-body&quot;&gt;
            {{ entry.body|safe }}
        &lt;/div&gt;
    &lt;/div&gt;
    {% endfor %}
{% else %}
    &lt;p&gt;&lt;strong&gt;{% trans &quot;No entry yet&quot; %}.&lt;/strong&gt;&lt;/p&gt;
{% endif %}
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive_year.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ year }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ year }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object_list %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive_month.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ month|date:&quot;Y/m&quot; }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ month|date:&quot;Y/m&quot; }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object_list %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive_day.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ day|date:&quot;Y/m/d&quot; }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ day|date:&quot;Y/m/d&quot; }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object_list %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_detail.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ object.title }}{% endblock title %}
{% block content %}
&lt;div class=&quot;entry&quot;&gt;
    &lt;h2&gt;{{ object.title }}&lt;/h2&gt;
    &lt;p class=&quot;entry-meta&quot;&gt;
        {{ object.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ object.category.get_absolute_url }}&quot;&gt;{{ object.category.name }}&lt;/a&gt;
    &lt;/p&gt;
    &lt;div class=&quot;entry-body&quot;&gt;
        {{ object.body|safe }}
    &lt;/div&gt;
&lt;/div&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/category_detail.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ object.name }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ object.name }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object.entry_set.all %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans ce dernier template, &lt;code&gt;object.entry_set.all&lt;/code&gt; récupére tous les billets
liés à cette catégorie. Et oui, tous. Y compris les billets hors ligne. Le plus
simple est donc de créer une propriété dans le modèle &lt;code&gt;Category&lt;/code&gt; pour ne
récupérer que les billets en ligne :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;class Category(models.Model):
    &quot;&quot;&quot;
    A blog category.
    &quot;&quot;&quot;
    name = models.CharField(_(&#39;name&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique=True)
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)

    objects = models.Manager()
    online_objects = CategoryOnlineManager()

    class Meta:
        verbose_name = _(&#39;category&#39;)
        verbose_name_plural = _(&#39;categories&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.name

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_category&#39;, (), {
            &#39;slug&#39;: self.slug,
        })

    def _get_online_entries(self):
        &quot;&quot;&quot;
        Returns entries in this category with status of &quot;online&quot;.
        Access this through the property ``online_entry_set``.
        &quot;&quot;&quot;
        from website.apps.blog.models import Entry
        return self.entry_set.filter(status=Entry.STATUS_ONLINE)

    online_entry_set = property(_get_online_entries)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voici la nouvelle version du template :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ object.name }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ object.name }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object.online_entry_set.all %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nos templates sont en place. C’est très rustre mais c’est un exemple
d’application.&lt;/p&gt;
&lt;p&gt;Qu’avons-nous oublié ? Mais oui, bien sûr, les fils RSS ! Un blog sans fils
RSS n’est pas un blog. C’est indispensable. Heureusement, Django nous permet
d’ajouter cette fonctionnalité en moins de deux minutes. Voyons comment procéder.&lt;/p&gt;
&lt;h2 id=&quot;ajout-des-fils-rss&quot;&gt;Ajout des fils RSS&lt;/h2&gt;
&lt;p&gt;Django embarque une application pour la génération de fils RSS. N’hésitez pas
à consulter la section &lt;a href=&quot;http://docs.djangoproject.com/en/1.2/ref/contrib/syndication/#ref-contrib-syndication&quot;&gt;The syndication feed framework&lt;/a&gt;
de la documentation pour en savoir plus. Difficile de faire plus simple et efficace.&lt;/p&gt;
&lt;p&gt;Commençons par écrire nos tests. Ajoutons les méthodes &lt;code&gt;test_rss_entries&lt;/code&gt; et
&lt;code&gt;test_rss_category&lt;/code&gt; à notre module &lt;code&gt;test.py&lt;/code&gt;, placé dans le répertoire de
l’application :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def test_rss_entries(self):
    &quot;&quot;&quot;
    Tests entries RSS feed.
    &quot;&quot;&quot;
    blog_url = reverse(&#39;blog&#39;)
    url = u&#39;%sfeed/rss/entries/&#39; % blog_url
    response = self.client.get(url)
    self.failUnlessEqual(response.status_code, 200)

def test_rss_category(self):
    &quot;&quot;&quot;
    Tests categories RSS feed.
    &quot;&quot;&quot;
    from website.apps.blog.models import Category
    categories = Category.online_objects.all()
    blog_url = reverse(&#39;blog&#39;)
    for category in categories:
        url = u&#39;%sfeed/rss/category/%s/&#39; % (blog_url, category.slug)
        response = self.client.get(url)
        self.failUnlessEqual(response.status_code, 200)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans notre application &lt;code&gt;blog&lt;/code&gt;, créons un module &lt;code&gt;feeds.py&lt;/code&gt;. Dans ce module,
plaçons nos classes de syndication :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8
&quot;&quot;&quot;
Feeds of ``blog`` application.
&quot;&quot;&quot;
from django.utils.feedgenerator import Rss201rev2Feed
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse

from django.contrib.syndication import feeds
from django.contrib.sites.models import Site

from website.apps.blog.models import Entry
from website.apps.blog.models import Category


class RssEntries(feeds.Feed):
    &quot;&quot;&quot;
    RSS entries.
    &quot;&quot;&quot;
    feed_type = Rss201rev2Feed
    title_template = &quot;blog/feeds/entry_title.html&quot;
    description_template = &quot;blog/feeds/entry_description.html&quot;

    def title(self):
        &quot;&quot;&quot;
        Channel title.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;%(site_name)s: RSS entries&#39;) % {
            &#39;site_name&#39;: site.name,
        }

    def description(self):
        &quot;&quot;&quot;
        Channel description.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;RSS feed of recent entries posted on %(site_name)s.&#39;) % {
            &#39;site_name&#39;: site.name,
        }

    def link(self):
        &quot;&quot;&quot;
        Channel link.
        &quot;&quot;&quot;
        return reverse(&#39;blog&#39;)

    def items(self):
        &quot;&quot;&quot;
        Channel items.
        &quot;&quot;&quot;
        return Entry.online_objects.order_by(&#39;-publication_date&#39;)[:10]

    def item_pubdate(self, item):
        &quot;&quot;&quot;
        Channel item publication date.
        &quot;&quot;&quot;
        return item.publication_date


class RssCategory(RssEntries):
    &quot;&quot;&quot;
    RSS category.
    &quot;&quot;&quot;
    def title(self, obj):
        &quot;&quot;&quot;
        Channel title.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;%(site_name)s: RSS %(category)s category&#39;) % {
            &#39;site_name&#39;: site.name,
            &#39;category&#39;: obj.name,
        }

    def description(self, obj):
        &quot;&quot;&quot;
        Channel description.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;RSS feed of recent entries posted in the category %(category)s on %(site_name)s.&#39;) % {
            &#39;category&#39;: obj.name,
            &#39;site_name&#39;: site.name,
        }

    def link(self, obj):
        &quot;&quot;&quot;
        Channel link.
        &quot;&quot;&quot;
        return reverse(&#39;blog_category&#39;, args=[obj.slug])

    def get_object(self, bits):
        &quot;&quot;&quot;
        Object: the Category.
        &quot;&quot;&quot;
        if len(bits) != 1:
            raise ObjectDoesNotExist
        return Category.online_objects.get(slug=bits[0])

    def items(self, obj):
        &quot;&quot;&quot;
        Channel items.
        &quot;&quot;&quot;
        return obj.online_entry_set

    def item_pubdate(self, item):
        &quot;&quot;&quot;
        Channel item publication date.
        &quot;&quot;&quot;
        return item.publication_date
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans le module &lt;code&gt;urls.py&lt;/code&gt; de l’application &lt;code&gt;blog&lt;/code&gt;, ajoutons le support des
fils RSS :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&quot;&quot;&quot;
URLs of blog application.
&quot;&quot;&quot;
from django.conf.urls.defaults import *

from website.apps.blog.models import Entry
from website.apps.blog.models import Category

from website.apps.blog.feeds import RssEntries
from website.apps.blog.feeds import RssCategory


rss_feeds = {
    &#39;entries&#39;: RssEntries,
    &#39;category&#39;: RssCategory,
}

urlpatterns = patterns(&#39;&#39;,
    url(r&#39;^feed/rss/(?P&lt;url&gt;.*)/$&#39;,
        &#39;django.contrib.syndication.views.feed&#39;, {
            &#39;feed_dict&#39;: rss_feeds,
        },
        name=&#39;blog_rss_feed&#39;,
    ),
    ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il ne reste plus qu’à créer les templates :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir templates/blog/feeds
$ touch templates/blog/feeds/entry_title.html
$ touch templates/blog/feeds/entry_description.html
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le template &lt;code&gt;templates/blog/feeds/entry_title.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{{ obj.title }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le template &lt;code&gt;templates/blog/feeds/entry_description.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{{ obj.body|safe }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lançons les tests pour vérifier si tout est OK:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python manage.py test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Vous ne devriez pas rencontrer d’erreur.&lt;/p&gt;
&lt;p&gt;Lancez le serveur:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans votre navigateur, allez à ces adresses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://127.0.0.1:8000/feed/rss/entries/&quot;&gt;http://127.0.0.1:8000/feed/rss/entries/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://127.0.0.1:8000/feed/rss/category/test/&quot;&gt;http://127.0.0.1:8000/feed/rss/category/test/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vous devriez voir les fils. Merci Django !&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Cet exemple d’application Django est un blog simpliste, basique, lambda. Moult
fonctionnalités ont été volontairement omises (formatage du contenu des billets
avec une syntaxe wiki, gestion multi-catégories, support des tags, support du
format Atom pour les fils de syndication, amélioration de l’interface
d’administration, amélioration des templates à l’aide d’includes, support des
commentaires, ajout d’une barre de navigation… ) pour ne pas transformer ce
tutoriel en ouvrage technique. La documentation de Django est complète et
claire. N’hésitez pas à la consulter au moindre problème.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://github.com/gillesfabio/creer-un-blog-avec-django-1.2&quot;&gt;Télécharger&lt;/a&gt; le
source de l’application.&lt;/p&gt;

    </content>
    
    <category term="python" />
    
    <category term="django" />
    
    <category term="development" />
    
  </entry>
  
  <entry xml:lang="fr">
    <id>http://gillesfabio.com/blog/2009/01/29/creer-un-blog-avec-django</id>
    <link href="http://gillesfabio.com/blog/2009/01/29/creer-un-blog-avec-django" rel="alternate" type="text/html" />
    <title>Créer un blog avec Django</title>
    <published>2009-01-29T00:00:00.000Z</published>
    <updated>2009-01-29T00:00:00.000Z</updated>
    <author>
      <name>Gilles Fabio</name>
    </author>
    <content type="html">
      &lt;p&gt;Créer un blog avec &lt;a href=&quot;http://www.djangoproject.com&quot;&gt;Django&lt;/a&gt;, &lt;strong&gt;c’est simple&lt;/strong&gt;.
Oui, vraiment. Ce tutorial s’adresse aux personnes connaissant déjà un peu le
langage de programmation &lt;a href=&quot;http://www.python.org&quot;&gt;Python&lt;/a&gt; et disposant d’un
environnement de développement Django opérationnel.&lt;/p&gt;
&lt;p&gt;Brièvement, cela se résume à :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python (interpréteur, libraires… )&lt;/li&gt;
&lt;li&gt;Un gestionnaire de bases de données et son &lt;em&gt;binding&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Un terminal (ou “console”)&lt;/li&gt;
&lt;li&gt;Un navigateur web&lt;/li&gt;
&lt;li&gt;Un éditeur de texte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Si vous désirez installer Django sur votre machine, n’hésitez pas à consulter
la section &lt;a href=&quot;http://docs.djangoproject.com/en/dev/intro/install/#intro-install&quot;&gt;Quick install guide&lt;/a&gt;
de la documentation.&lt;/p&gt;
&lt;h2 id=&quot;mise-en-place-du-projet&quot;&gt;Mise en place du projet&lt;/h2&gt;
&lt;p&gt;Dans ce tutorial, nous allons utiliser &lt;a href=&quot;http://www.mysql.org&quot;&gt;MySQL&lt;/a&gt; comme
gestionnaire de bases de données mais vous pouvez utiliser n’importe quel autre
gestionnaire de bases de données supporté par Django.&lt;/p&gt;
&lt;p&gt;Il faut donc, tout d’abord, créer une base de données :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mysql -u user -p
mysql&gt; CREATE DATABASE blog;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remplacez &lt;code&gt;user&lt;/code&gt; par l’utilisateur qui va bien (root ou un utilisateur ayant
les droits de création de base). Comme il vaut mieux éviter d’utiliser
l’utilisateur root, même en local, nous allons créer un utilisateur spécifique
pour la base et lui donner les permissions nécessaires. Nous allons nommer cet
utilisateur &lt;code&gt;blog&lt;/code&gt;, du même nom que la base :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql&gt; GRANT ALL ON blog.* TO blog@localhost IDENTIFIED BY &#39;password&#39;;
mysql&gt; FLUSH PRIVILEGES;
mysql&gt; \q
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bien sûr, remplacez &lt;code&gt;password&lt;/code&gt; par un vrai mot de passe.&lt;/p&gt;
&lt;p&gt;Notre base est créée.&lt;/p&gt;
&lt;p&gt;Dans un dossier de votre répertoire personnel (si possible, dans un dossier
dédié à vos projets de programmation), nous allons créer le projet Django
&lt;code&gt;website&lt;/code&gt; et l’application &lt;code&gt;blog&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ django-admin startproject website
$ cd website
$ django-admin startapp blog
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans le répertoire du projet &lt;code&gt;website&lt;/code&gt;, nous allons créer un dossier
&lt;code&gt;templates&lt;/code&gt; (qui contiendra nos templates) et un dossier &lt;code&gt;media&lt;/code&gt; qui
contiendra les fichiers statiques :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir templates
$ mkdir media
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour bien séparer nos applications, nous allons les placer dans un répertoire
&lt;code&gt;apps&lt;/code&gt; à la racine du projet :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir apps
$ touch apps/__init__.py
$ mv blog apps/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;La commande &lt;code&gt;tree&lt;/code&gt; devrait retourner cette arborescence :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
|-- __init__.py
|-- apps
|   |-- __init__.py
|   `-- blog
|       |-- __init__.py
|       |-- models.py
|       `-- views.py
|-- manage.py
|-- media
|-- settings.py
|-- templates
`-- urls.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le projet est en place. Exécutez la commande suivante :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans votre navigateur, pointez l’adresse : &lt;a href=&quot;http://127.0.0.1:8000&quot;&gt;http://127.0.0.1:8000&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bienvenue sous Django ! Control + C pour stopper le serveur.&lt;/p&gt;
&lt;h2 id=&quot;param-tres-et-urls&quot;&gt;Paramètres et URLs&lt;/h2&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;settings.py&lt;/code&gt; à l’aide de votre éditeur favori.&lt;/p&gt;
&lt;p&gt;Tout d’abord, on crée la constante &lt;code&gt;PROJECT_PATH&lt;/code&gt; (en haut du fichier) afin
de stocker le chemin absolu vers notre projet :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;import os.path
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C’est pratique si vous utilisez différents systèmes d’exploitation ou
différentes machines. Le chemin est automatiquement détecté.&lt;/p&gt;
&lt;p&gt;Ensuite, on passe à la base de données :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;DATABASE_ENGINE = &#39;mysql&#39;
DATABASE_NAME = &#39;blog&#39;
DATABASE_USER = &#39;blog&#39;
DATABASE_PASSWORD = &#39;password&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajuste la &lt;em&gt;timezone&lt;/em&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;TIME_ZONE = &#39;Europe/Paris&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajuste la langue par défaut :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;LANGUAGE_CODE = &#39;fr-fr&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute notre répertoire &lt;code&gt;media&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;MEDIA_ROOT = os.path.join(PROJECT_PATH, &#39;media/&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute l’URL vers les médias :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;MEDIA_URL = &#39;/media/&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajuste l’URL vers les médias de l’interface d’administration :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;ADMIN_MEDIA_PREFIX = &#39;/media/admin/&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute notre répertoire &lt;code&gt;templates&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;TEMPLATE_DIRS = (
    os.path.join(PROJECT_PATH, &#39;templates&#39;),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On ajoute notre application &lt;code&gt;blog&lt;/code&gt; à la liste &lt;code&gt;INSTALLED_APPS&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;INSTALLED_APPS = (
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.sites&#39;,
    &#39;website.apps.blog&#39;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On enregistre les modifications et on passe aux URLs.&lt;/p&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;urls.py&lt;/code&gt;. Nous allons ajouter le support des médias. Django
va donc prendre en charge les fichiers statiques (pratique quand on développe
en local mais à proscrire en production). Pour ce faire, on importe le module
&lt;code&gt;settings&lt;/code&gt; pour récupérer &lt;code&gt;MEDIA_ROOT&lt;/code&gt; (le chemin absolu vers le répertoire
&lt;code&gt;media&lt;/code&gt;) et on ajoute un &lt;em&gt;urlpatterns&lt;/em&gt; pour &lt;code&gt;django.views.static.serve&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;from django.conf.urls.defaults import *
from django.conf import settings

# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()

urlpatterns = patterns(&#39;&#39;,
    # Example:
    # (r&#39;^website/&#39;, include(&#39;website.foo.urls&#39;)),

    # Uncomment the admin/doc line below and add &#39;django.contrib.admindocs&#39;
    # to INSTALLED_APPS to enable admin documentation:
    # (r&#39;^admin/doc/&#39;, include(&#39;django.contrib.admindocs.urls&#39;)),

    # Uncomment the next line to enable the admin:
    # (r&#39;^admin/(.*)&#39;, admin.site.root),
)

urlpatterns += patterns(&#39;&#39;,
    (r&#39;^media/(?P&lt;path&gt;.*)$&#39;,
        &#39;django.views.static.serve&#39;, {
            &#39;document_root&#39;: settings.MEDIA_ROOT,
            &#39;show_indexes&#39;: True,
        },
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On enregistre les modifications et on passe à l’installation de l’interface
d’administration.&lt;/p&gt;
&lt;h2 id=&quot;installation-de-l-interface-d-administration&quot;&gt;Installation de l’interface d’administration&lt;/h2&gt;
&lt;p&gt;Django embarque une interface d’administration sympathique et pratique.
L’installation se fait en trois étapes : ajout de l’application dans le fichier
&lt;code&gt;settings.py&lt;/code&gt;, ajout des URLs et synchronisation de la base de données.&lt;/p&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;settings.py&lt;/code&gt; et ajoutez &lt;code&gt;django.contrib.admin&lt;/code&gt; dans la liste
&lt;code&gt;INSTALLED_APPS&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;INSTALLED_APPS = (
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.sites&#39;,
    &#39;django.contrib.admin&#39;,
    &#39;website.apps.blog&#39;,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enregistrez les modifications.&lt;/p&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;urls.py&lt;/code&gt; et ajoutez le support de l’admin en décommentant les
lignes indiquées dans les commentaires, soit trois lignes au total :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;from django.conf.urls.defaults import *
from django.conf import settings

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns(&#39;&#39;,
    # Example:
    # (r&#39;^website/&#39;, include(&#39;website.foo.urls&#39;)),

    # Uncomment the admin/doc line below and add &#39;django.contrib.admindocs&#39;
    # to INSTALLED_APPS to enable admin documentation:
    # (r&#39;^admin/doc/&#39;, include(&#39;django.contrib.admindocs.urls&#39;)),

    # Uncomment the next line to enable the admin:
    (r&#39;^admin/(.*)&#39;, admin.site.root),
)

urlpatterns += patterns(&#39;&#39;,
    (r&#39;^media/(?P&lt;path&gt;.*)$&#39;,
        &#39;django.views.static.serve&#39;, {
            &#39;document_root&#39;: settings.MEDIA_ROOT,
            &#39;show_indexes&#39;: True,
        },
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enregistrez les modifications.&lt;/p&gt;
&lt;p&gt;Il ne reste plus qu’à synchroniser avec la base de données (à exécuter à la
racine du projet) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py syncdb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Django vous guidera dans la création d’un compte super-utilisateur.&lt;/p&gt;
&lt;p&gt;Lancez le serveur :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans votre navigateur, pointer l’adresse : &lt;a href=&quot;http://127.0.0.1:8000/admin/&quot;&gt;http://127.0.0.1:8000/admin/&lt;/a&gt;. Entrez
votre identifiant et votre mot de passe super-utilisateur.  Bienvenue dans
l’interface d’administration de Django !&lt;/p&gt;
&lt;h2 id=&quot;-criture-des-tests&quot;&gt;Écriture des tests&lt;/h2&gt;
&lt;p&gt;Oui, écrire les tests avant le code, c’est mieux. Ça permet d’éviter des bogues
et des prises de tête. Le but est le suivant : faire en sorte que tous les tests
passent. Prêt ? Alors créez un fichier &lt;code&gt;tests.py&lt;/code&gt; à la racine de l’application
&lt;code&gt;blog&lt;/code&gt;. On commence par importer la classe &lt;code&gt;TestCase&lt;/code&gt; du module &lt;code&gt;django.test&lt;/code&gt;
et on crée une classe &lt;code&gt;BlogTest&lt;/code&gt; qui contiendra nos tests : ::&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
from django.test import TestCase

class BlogTest(TestCase):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On lance les tests (à exécuter à la racine du projet) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py test
Creating test database...
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log
Installing index for auth.Permission model
Installing index for auth.Message model
Installing index for admin.LogEntry model
................
----------------------------------------------------------------------
Ran 16 tests in 2.134s

OK
Destroying test database...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tous les tests passent ! Normal, nous n’en avons écrit aucun. Au boulot !&lt;/p&gt;
&lt;p&gt;Notre classe de test :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
from django.test import TestCase
from django.core.urlresolvers import reverse

class BlogTest(TestCase):
    &quot;&quot;&quot;
    Tests of ``blog`` application.
    &quot;&quot;&quot;
    fixtures = [&#39;test_data&#39;]

    def test_entry_archive_index(self):
        &quot;&quot;&quot;
        Tests ``entry_archive`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog&#39;))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive.html&#39;)

    def test_entry_archive_year(self):
        &quot;&quot;&quot;
        Tests ``entry_archive_year`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_year&#39;, args=[&#39;2009&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive_year.html&#39;)

    def test_entry_archive_month(self):
        &quot;&quot;&quot;
        Tests ``entry_archive_month``view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_month&#39;, args=[&#39;2009&#39;, &#39;01&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive_month.html&#39;)

    def test_entry_archive_day(self):
        &quot;&quot;&quot;
        Tests ``entry_archive_day`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_day&#39;, args=[&#39;2009&#39;, &#39;01&#39;, &#39;28&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_archive_day.html&#39;)

    def test_entry_detail(self):
        &quot;&quot;&quot;
        Tests ``entry_detail`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_entry&#39;, args=[&#39;2009&#39;, &#39;01&#39;, &#39;28&#39;, &#39;test-entry&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/entry_detail.html&#39;)

    def test_entry_detail_not_found(self):
        &quot;&quot;&quot;
        Test ``entry_detail`` view with an offline entry.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_entry&#39;, args=[&#39;2009&#39;, &#39;01&#39;, &#39;28&#39;, &#39;offline-entry&#39;]))
        self.failUnlessEqual(response.status_code, 404)

    def test_category_detail(self):
        &quot;&quot;&quot;
        Tests ``category_detail`` view.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_category&#39;, args=[&#39;test&#39;]))
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed(response, &#39;blog/category_detail.html&#39;)

    def test_category_detail_not_found(self):
        &quot;&quot;&quot;
        Tests ``category_detail`` view with an offline category.
        &quot;&quot;&quot;
        response = self.client.get(reverse(&#39;blog_category&#39;, args=[&#39;offline&#39;]))
        self.failUnlessEqual(response.status_code, 404)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La fonction &lt;code&gt;reverse&lt;/code&gt; est utilisée pour récupérer l’URL en fonction de son
nom (URLs nommées). Pour en savoir plus, n’hésitez pas à consulter la section
&lt;a href=&quot;http://docs.djangoproject.com/en/dev/topics/http/urls/&quot;&gt;URL dispatcher&lt;/a&gt; de la
documentation. Nous testons ici la réponse et le template (pour vérifier que la
future vue renverra bien le bon template). Si vous relancez les tests, vous
devriez vous faire insulter. C’est normal. Nous n’avons encore rien implémenté.
Donc, passons à l’implémentation.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-mod-les&quot;&gt;Création des modèles&lt;/h2&gt;
&lt;p&gt;Nous allons réaliser un blog “basique” composé de deux modèles : &lt;code&gt;Entry&lt;/code&gt; et
&lt;code&gt;Category&lt;/code&gt;. Le premier modèle représente un billet de blog et le deuxième une
catégorie pour classer les billets par thème.&lt;/p&gt;
&lt;p&gt;Un billet est composé des champs suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un titre&lt;/li&gt;
&lt;li&gt;Un slug (aussi appelé “permalien”)&lt;/li&gt;
&lt;li&gt;Un auteur&lt;/li&gt;
&lt;li&gt;Une catégorie&lt;/li&gt;
&lt;li&gt;Une date de création&lt;/li&gt;
&lt;li&gt;Une date de modification&lt;/li&gt;
&lt;li&gt;Une date de publication&lt;/li&gt;
&lt;li&gt;Un statut (en ligne / hors ligne)&lt;/li&gt;
&lt;li&gt;Un corps au format HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une categorie est composée des champs suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un nom&lt;/li&gt;
&lt;li&gt;Un slug (aussi appelé “permalien”)&lt;/li&gt;
&lt;li&gt;Une date de création&lt;/li&gt;
&lt;li&gt;Une date de modification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Éditez le fichier &lt;code&gt;models.py&lt;/code&gt; du répertoire &lt;code&gt;blog&lt;/code&gt;. Ce fichier contiendra les
modèles de notre application. Pour en savoir plus, n’hésitez pas à consulter
la section &lt;a href=&quot;http://docs.djangoproject.com/en/dev/topics/db/models/#topics-db-models&quot;&gt;Writing models&lt;/a&gt;
de la documentation.&lt;/p&gt;
&lt;p&gt;Nos modèles :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Models of ``blog`` application.
&quot;&quot;&quot;
from datetime import datetime

from django.db import models
from django.utils.translation import ugettext_lazy as _

class Category(models.Model):
    &quot;&quot;&quot;
    A blog category.
    &quot;&quot;&quot;
    name = models.CharField(_(&#39;name&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique=True)
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)

    class Meta:
        verbose_name = _(&#39;category&#39;)
        verbose_name_plural = _(&#39;categories&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.name

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_category&#39;, (), {
            &#39;slug&#39;: self.slug,
        })

class Entry(models.Model):
    &quot;&quot;&quot;
    A blog entry.
    &quot;&quot;&quot;
    STATUS_OFFLINE = 0
    STATUS_ONLINE = 1
    STATUS_DEFAULT = STATUS_OFFLINE
    STATUS_CHOICES = (
        (STATUS_OFFLINE, _(&#39;Offline&#39;)),
        (STATUS_ONLINE, _(&#39;Online&#39;)),
    )

    title = models.CharField(_(&#39;title&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique_for_date=&#39;publication_date&#39;)
    author = models.ForeignKey(&#39;auth.User&#39;, verbose_name=_(&#39;author&#39;))
    category = models.ForeignKey(Category, verbose_name=_(&#39;category&#39;))
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)
    publication_date = models.DateTimeField(_(&#39;publication date&#39;), default=datetime.now(), db_index=True)
    status = models.IntegerField(_(&#39;status&#39;), choices=STATUS_CHOICES, default=STATUS_DEFAULT, db_index=True)
    body = models.TextField(_(&#39;body&#39;))

    class Meta:
        verbose_name = _(&#39;entry&#39;)
        verbose_name_plural = _(&#39;entries&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.title

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_entry&#39;, (), {
            &#39;year&#39;: self.publication_date.strftime(&#39;%Y&#39;),
            &#39;month&#39;: self.publication_date.strftime(&#39;%m&#39;),
            &#39;day&#39;: self.publication_date.strftime(&#39;%d&#39;),
            &#39;slug&#39;: self.slug,
        })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La syntaxe du langage Python est tellement &lt;em&gt;clean&lt;/em&gt; que le code parle de lui-même.&lt;/p&gt;
&lt;p&gt;Nos modèles sont &lt;em&gt;i18n-ready&lt;/em&gt; (via la fonction magique &lt;code&gt;_()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Avant de créer nos tables, il est recommandé de vérifier si les modèles ne
comportent aucune erreur. Pour ce faire, à la racine du projet, on exécute
la commande suivante :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py validate
0 errors found
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si cette commande renvoie des erreurs, il suffira de les corriger.&lt;/p&gt;
&lt;p&gt;Tout est OK. On synchronise avec la base de données:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py syncdb
Creating table blog_category
Creating table blog_entry
Installing index for blog.Entry model
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Par la suite, dans nos templates, nous afficheront uniquement les billets ayant
pour statut “en ligne”. Lors de la récupération de nos objets, on peut très bien
filtrer sur ce champ. Mais parce qu’on est feignant, on va créer des &lt;em&gt;managers&lt;/em&gt;
pour s’épargner du code.&lt;/p&gt;
&lt;p&gt;Les méthodes d’un manager s’appliquent à une table, tandis que les méthodes d’un
modèle s’appliquent à un objet. Donc, si nous voulons récupérer tous les billets
ayant pour statut “en ligne”, nous avons besoin d’un manager. Si nous voulons
récupérer le nom complet de l’auteur du billet, nous devons définir une méthode
spécifique dans le modèle.&lt;/p&gt;
&lt;p&gt;Nous avons besoin de deux managers : un pour manipuler uniquement les billets
“en ligne” et un autre pour manipuler uniquement les catégories ayant des billets
“en ligne” (c’est-à-dire que si nous rédigeons un seul billet dans une catégorie et
que ce billet est “hors ligne”, la catégorie ne doit pas exister publiquement).&lt;/p&gt;
&lt;p&gt;Dans le répertoire de notre application, on crée un fichier (ou plutôt, un &lt;em&gt;module&lt;/em&gt;)
nommé &lt;code&gt;managers.py&lt;/code&gt;. N’hésitez pas à consulter la section &lt;a href=&quot;http://docs.djangoproject.com/en/dev/topics/db/managers/&quot;&gt;Managers&lt;/a&gt;
de la documentation pour en savoir plus.&lt;/p&gt;
&lt;p&gt;Nos managers :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Managers of ``blog`` application.
&quot;&quot;&quot;
from django.db import models

class CategoryOnlineManager(models.Manager):
    &quot;&quot;&quot;
    Manager that manages online ``Category`` objects.
    &quot;&quot;&quot;

    def get_query_set(self):
        from website.apps.blog.models import Entry
        entry_status = Entry.STATUS_ONLINE
        return super(CategoryOnlineManager, self).get_query_set().filter(
            entry__status=entry_status).distinct()

class EntryOnlineManager(models.Manager):
    &quot;&quot;&quot;
    Manager that manages online ``Entry`` objects.
    &quot;&quot;&quot;

    def get_query_set(self):
        return super(EntryOnlineManager, self).get_query_set().filter(
            status=self.model.STATUS_ONLINE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il faut maintenant ajouter ces managers dans nos modèles :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Models of ``blog`` application.
&quot;&quot;&quot;
from datetime import datetime

from django.db import models
from django.utils.translation import ugettext_lazy as _

from website.apps.blog.managers import CategoryOnlineManager
from website.apps.blog.managers import EntryOnlineManager

class Category(models.Model):
    &quot;&quot;&quot;
    A blog category.
    &quot;&quot;&quot;
    name = models.CharField(_(&#39;name&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique=True)
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)

    objects = models.Manager()
    online_objects = CategoryOnlineManager()

    class Meta:
        verbose_name = _(&#39;category&#39;)
        verbose_name_plural = _(&#39;categories&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.name

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_category&#39;, (), {
            &#39;slug&#39;: self.slug,
        })

class Entry(models.Model):
    &quot;&quot;&quot;
    A blog entry.
    &quot;&quot;&quot;
    STATUS_OFFLINE = 0
    STATUS_ONLINE = 1
    STATUS_DEFAULT = STATUS_OFFLINE
    STATUS_CHOICES = (
        (STATUS_OFFLINE, _(&#39;Offline&#39;)),
        (STATUS_ONLINE, _(&#39;Online&#39;)),
    )

    title = models.CharField(_(&#39;title&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique_for_date=&#39;publication_date&#39;)
    author = models.ForeignKey(&#39;auth.User&#39;, verbose_name=_(&#39;author&#39;))
    category = models.ForeignKey(Category, verbose_name=_(&#39;category&#39;))
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)
    publication_date = models.DateTimeField(_(&#39;publication date&#39;), default=datetime.now(), db_index=True)
    status = models.IntegerField(_(&#39;status&#39;), choices=STATUS_CHOICES, default=STATUS_DEFAULT, db_index=True)
    body = models.TextField(_(&#39;body&#39;))

    objects = models.Manager()
    online_objects = EntryOnlineManager()

    class Meta:
        verbose_name = _(&#39;entry&#39;)
        verbose_name_plural = _(&#39;entries&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.title

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_entry&#39;, (), {
            &#39;year&#39;: self.publication_date.strftime(&#39;%Y&#39;),
            &#39;month&#39;: self.publication_date.strftime(&#39;%m&#39;),
            &#39;day&#39;: self.publication_date.strftime(&#39;%d&#39;),
            &#39;slug&#39;: self.slug,
        })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nous avons des modèles, des managers, une interface d’administration… Ah tiens,
et si on ajoutait nos modèles dans l’admin ? Il serait peut-être temps de
rédiger quelques billets et de créer quelques catégories pour nos tests.&lt;/p&gt;
&lt;h2 id=&quot;ajout-des-mod-les-dans-l-interface-d-administration&quot;&gt;Ajout des modèles dans l’interface d’administration&lt;/h2&gt;
&lt;p&gt;Pour ajouter nos modèles dans l’interface d’administration, nous devons créer
une classe de type &lt;code&gt;ModelAdmin&lt;/code&gt; par modèle. Chaque classe embarquera des
options et des méthodes propres à l’admin. Par convention, on placera ces
classes dans un module &lt;code&gt;admin.py&lt;/code&gt; dans le répertoire de l’application.
N’hésitez pas à consulter la section &lt;a href=&quot;http://docs.djangoproject.com/en/dev/ref/contrib/admin/&quot;&gt;The Django admin site&lt;/a&gt;
de la documentation pour en savoir plus.&lt;/p&gt;
&lt;p&gt;Créez le fichier &lt;code&gt;admin.py&lt;/code&gt; dans le répertoire &lt;code&gt;blog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Nos classes admin :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Administration interface options of ``blog`` application.
&quot;&quot;&quot;
from django.contrib import admin

from website.apps.blog.models import Category
from website.apps.blog.models import Entry

class CategoryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Category`` model.
    &quot;&quot;&quot;
    pass

class EntryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Entry`` model.
    &quot;&quot;&quot;
    pass

admin.site.register(Category, CategoryAdmin)
admin.site.register(Entry, EntryAdmin)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pour l’instant, on se contente du minimum. Il est possible de presque tout
personnaliser. Nous allons quand même améliorer un peu. Voici une version un
peu plus peaufinée :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
Administration interface options of ``blog`` application.
&quot;&quot;&quot;
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _

from website.apps.blog.models import Category
from website.apps.blog.models import Entry

class CategoryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Category`` model.
    &quot;&quot;&quot;
    list_display = (&#39;name&#39;, &#39;slug&#39;, &#39;creation_date&#39;, &#39;modification_date&#39;)
    search_fields = (&#39;name&#39;,)
    date_hierarchy = &#39;creation_date&#39;
    save_on_top = True
    prepopulated_fields = {&#39;slug&#39;: (&#39;name&#39;,)}

class EntryAdmin(admin.ModelAdmin):
    &quot;&quot;&quot;
    Administration interface options of ``Entry`` model.
    &quot;&quot;&quot;
    list_display = (&#39;title&#39;, &#39;category&#39;, &#39;status&#39;, &#39;author&#39;)
    search_fields = (&#39;title&#39;, &#39;body&#39;)
    date_hierarchy = &#39;publication_date&#39;
    fieldsets = (
        (_(&#39;Headline&#39;), {&#39;fields&#39;: (&#39;author&#39;, &#39;title&#39;, &#39;slug&#39;, &#39;category&#39;)}),
        (_(&#39;Publication&#39;), {&#39;fields&#39;: (&#39;publication_date&#39;, &#39;status&#39;)}),
        (_(&#39;Body&#39;), {&#39;fields&#39;: (&#39;body&#39;,)}),
    )
    save_on_top = True
    radio_fields = {&#39;status&#39;: admin.VERTICAL}
    prepopulated_fields = {&#39;slug&#39;: (&#39;title&#39;,)}

admin.site.register(Category, CategoryAdmin)
admin.site.register(Entry, EntryAdmin)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Maintenant qu’on peut créer des billets et des catégories, nous allons en
profiter pour créer des fixtures pour nos tests. Les fixtures sont des données
de test.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-fixtures&quot;&gt;Création des fixtures&lt;/h2&gt;
&lt;p&gt;On crée deux catégories. La première :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Test&lt;/li&gt;
&lt;li&gt;Slug : test&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La seconde :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Offline&lt;/li&gt;
&lt;li&gt;Slug : offline&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et deux billets. Le premier :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Test Entry&lt;/li&gt;
&lt;li&gt;Slug : test-entry&lt;/li&gt;
&lt;li&gt;Catégorie : Test&lt;/li&gt;
&lt;li&gt;Date de publication : 2009-01-28 00:00:00&lt;/li&gt;
&lt;li&gt;Statut : en ligne&lt;/li&gt;
&lt;li&gt;Corps : peu importe, ce que vous voulez&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le second :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Titre : Offline&lt;/li&gt;
&lt;li&gt;Slug : offline-entry&lt;/li&gt;
&lt;li&gt;Catégorie : Offline&lt;/li&gt;
&lt;li&gt;Date de publication : 2009-01-28 00:00:00&lt;/li&gt;
&lt;li&gt;Statut : hors ligne&lt;/li&gt;
&lt;li&gt;Corps : peu importe, ce que vous voulez&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une fois ces données sauvegardées, on va les exporter au format JSON pour
pouvoir les réutiliser automatiquement dans nos tests.&lt;/p&gt;
&lt;p&gt;Créons tout d’abord un répertoire &lt;code&gt;fixtures&lt;/code&gt; dans le répertoire de l’application.&lt;/p&gt;
&lt;p&gt;Puis, exécutez cette commande à la racine du projet :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py dumpdata blog --indent=4 &gt; apps/blog/fixtures/test_data.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nos fixtures sont prêtes. Passons aux URLs.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-urls&quot;&gt;Création des URLs&lt;/h2&gt;
&lt;p&gt;Nous n’avons même pas besoin de créer de vue pour notre application puisque nous
allons utiliser les vues génériques de Django. N’hésitez pas à consulter les
sections &lt;a href=&quot;http://docs.djangoproject.com/en/dev/ref/generic-views/#ref-generic-views&quot;&gt;Generic Views&lt;/a&gt;
et &lt;a href=&quot;http://docs.djangoproject.com/en/dev/topics/http/urls/&quot;&gt;URL dispatcher&lt;/a&gt; de la
documentation pour en savoir plus.&lt;/p&gt;
&lt;p&gt;Par convention, les URLs seront contenues dans le module &lt;code&gt;urls.py&lt;/code&gt; dans le
répertoire de l’application (ce fichier n’existe pas, donc pensez à le créer).&lt;/p&gt;
&lt;p&gt;Nos URLs :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8 -*-
&quot;&quot;&quot;
URLs of ``blog`` application.
&quot;&quot;&quot;
from django.conf.urls.defaults import *

from website.apps.blog.models import Entry
from website.apps.blog.models import Category

urlpatterns = patterns(&#39;&#39;,
    url(r&#39;^(?P&lt;year&gt;\d{4})/(?P&lt;month&gt;\d{2})/(?P&lt;day&gt;\d{2})/(?P&lt;slug&gt;[\w-]+)/$&#39;,
        &#39;django.views.generic.date_based.object_detail&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            month_format=&#39;%m&#39;,
            date_field=&#39;publication_date&#39;,
            slug_field=&#39;slug&#39;,
        ),
        name=&#39;blog_entry&#39;,
    ),
    url(r&#39;^(?P&lt;year&gt;\d{4})/(?P&lt;month&gt;\d{2})/(?P&lt;day&gt;\d{2})/$&#39;,
        &#39;django.views.generic.date_based.archive_day&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            month_format=&#39;%m&#39;,
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog_day&#39;,
    ),
    url(r&#39;^(?P&lt;year&gt;\d{4})/(?P&lt;month&gt;\d{2})/$&#39;,
        &#39;django.views.generic.date_based.archive_month&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            month_format=&#39;%m&#39;,
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog_month&#39;,
    ),
    url(r&#39;^(?P&lt;year&gt;\d{4})/$&#39;,
        &#39;django.views.generic.date_based.archive_year&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            make_object_list=True,
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog_year&#39;,
    ),
    url(r&#39;^category/(?P&lt;slug&gt;[\w-]+)/$&#39;,
        &#39;django.views.generic.list_detail.object_detail&#39;,
        dict(
            queryset=Category.online_objects.all(),
            slug_field=&#39;slug&#39;
        ),
        name=&#39;blog_category&#39;,
    ),
    url(r&#39;^$&#39;,
        &#39;django.views.generic.date_based.archive_index&#39;,
        dict(
            queryset=Entry.online_objects.all(),
            date_field=&#39;publication_date&#39;,
        ),
        name=&#39;blog&#39;,
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notre projet n’est pas encore au courant de ces URLs. Éditez le fichier &lt;code&gt;urls.py&lt;/code&gt;
à la racine du projet et ajoutez le module via la fonction &lt;code&gt;include&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;from django.conf.urls.defaults import *
from django.conf import settings

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns(&#39;&#39;,
    # Example:
    # (r&#39;^website/&#39;, include(&#39;website.foo.urls&#39;)),
    (r&#39;&#39;, include(&#39;website.apps.blog.urls&#39;)),

    # Uncomment the admin/doc line below and add &#39;django.contrib.admindocs&#39;
    # to INSTALLED_APPS to enable admin documentation:
    # (r&#39;^admin/doc/&#39;, include(&#39;django.contrib.admindocs.urls&#39;)),

    # Uncomment the next line to enable the admin:
    (r&#39;^admin/(.*)&#39;, admin.site.root),
)

urlpatterns += patterns(&#39;&#39;,
    (r&#39;^media/(?P&lt;path&gt;.*)$&#39;,
        &#39;django.views.static.serve&#39;,
        {
            &#39;document_root&#39;: settings.MEDIA_ROOT,
            &#39;show_indexes&#39;: True,
        },
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Relançons nos tests :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ça ne passe toujours pas mais vous avez certainement remarqué que les erreurs
sont différentes. Vous ne devriez voir que des erreurs de templates. Il y a
donc une progression !&lt;/p&gt;
&lt;p&gt;Passons à la création des templates.&lt;/p&gt;
&lt;h2 id=&quot;cr-ation-des-templates&quot;&gt;Création des templates&lt;/h2&gt;
&lt;p&gt;Nous allons, dans un premier temps, créer uniquement des templates vides. Puis,
nous relancerons nos tests pour vérifier si ils passent bien à présent. Il
restera alors juste à remplir les templates pour afficher les données.&lt;/p&gt;
&lt;p&gt;On se place à la racine du projet et on crée les fichiers :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir templates/layout
$ mkdir templates/blog
$ touch templates/layout/base.html
$ touch templates/blog/entry_detail.html
$ touch templates/blog/category_detail.html
$ touch templates/blog/entry_archive.html
$ touch templates/blog/entry_archive_year.html
$ touch templates/blog/entry_archive_month.html
$ touch templates/blog/entry_archive_day.html
$ touch templates/404.html
$ touch templates/500.html
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Relançons les tests :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python manage.py test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Nos tests passent ! Nous devons maintenant remplir ces templates.&lt;/p&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/layout/base.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;&lt;!DOCTYPE html PUBLIC &#39;-//W3C//DTD XHTML 1.0 Strict//EN&#39; &#39;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&#39;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;fr&quot; lang=&quot;fr&quot;&gt;
    &lt;head&gt;
        &lt;title&gt;{% block title %}{% endblock title %} - Django Blog&lt;/title&gt;
        &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;
        &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;all&quot; href=&quot;{{ MEDIA_URL }}css/screen.css&quot; /&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id=&quot;header&quot;&gt;
            &lt;h1&gt;&lt;a href=&quot;{% url blog %}&quot;&gt;Django Blog&lt;/a&gt;&lt;/h1&gt;
        &lt;/div&gt;
        &lt;div id=&quot;content&quot;&gt;
            {% block content %}{% endblock content %}
        &lt;/div&gt;
    &lt;/body&gt;
&lt;/html&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/404.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% load i18n %}
{% block title %}{% trans &quot;404 Not Found&quot; %}{% endblock title %}
{% block content %}
    &lt;p&gt;&lt;strong&gt;{% trans &quot;404 Not Found&quot; %}&lt;/strong&gt;&lt;/p&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/500.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% load i18n %}
{% block title %}{% trans &quot;error 500&quot; %}{% endblock title %}
{% block content %}
    &lt;p&gt;&lt;strong&gt;{% trans &quot;Error 500&quot; %}&lt;/strong&gt;&lt;/p&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% load i18n %}
{% block title %}{% trans &quot;Latest entries&quot; %}{% endblock title %}
{% block content %}
&lt;h2&gt;{% trans &quot;Latest entries&quot; %}&lt;/h2&gt;
{% if latest %}
    {% for entry in latest %}
    &lt;div class=&quot;entry&quot;&gt;
        &lt;h3&gt;&lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt;&lt;/h3&gt;
        &lt;p class=&quot;entry-meta&quot;&gt;
            {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
            &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/p&gt;
        &lt;div class=&quot;entry-body&quot;&gt;
            {{ entry.body|safe }}
        &lt;/div&gt;
    &lt;/div&gt;
    {% endfor %}
{% else %}
    &lt;p&gt;&lt;strong&gt;{% trans &quot;No entry yet&quot; %}.&lt;/strong&gt;&lt;/p&gt;
{% endif %}
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive_year.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ year }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ year }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object_list %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive_month.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ month|date:&quot;Y/m&quot; }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ month|date:&quot;Y/m&quot; }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object_list %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_archive_day.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ day|date:&quot;Y/m/d&quot; }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ day|date:&quot;Y/m/d&quot; }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object_list %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/entry_detail.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ object.title }}{% endblock title %}
{% block content %}
&lt;div class=&quot;entry&quot;&gt;
    &lt;h2&gt;{{ object.title }}&lt;/h2&gt;
    &lt;p class=&quot;entry-meta&quot;&gt;
        {{ object.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ object.category.get_absolute_url }}&quot;&gt;{{ object.category.name }}&lt;/a&gt;
    &lt;/p&gt;
    &lt;div class=&quot;entry-body&quot;&gt;
        {{ object.body|safe }}
    &lt;/div&gt;
&lt;/div&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fichier &lt;code&gt;templates/blog/category_detail.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ object.name }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ object.name }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object.entry_set.all %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans ce dernier template, &lt;code&gt;object.entry_set.all&lt;/code&gt; récupére tous les billets
liés à cette catégorie. Et oui, tous. Y compris les billets hors ligne. Le plus
simple est donc de créer une propriété dans le modèle &lt;code&gt;Category&lt;/code&gt; pour ne
récupérer que les billets en ligne :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;class Category(models.Model):
    &quot;&quot;&quot;
    A blog category.
    &quot;&quot;&quot;
    name = models.CharField(_(&#39;name&#39;), max_length=255)
    slug = models.SlugField(_(&#39;slug&#39;), max_length=255, unique=True)
    creation_date = models.DateTimeField(_(&#39;creation date&#39;), auto_now_add=True)
    modification_date = models.DateTimeField(_(&#39;modification date&#39;), auto_now=True)

    objects = models.Manager()
    online_objects = CategoryOnlineManager()

    class Meta:
        verbose_name = _(&#39;category&#39;)
        verbose_name_plural = _(&#39;categories&#39;)

    def __unicode__(self):
        return u&#39;%s&#39; % self.name

    @models.permalink
    def get_absolute_url(self):
        return (&#39;blog_category&#39;, (), {
            &#39;slug&#39;: self.slug,
        })

    def _get_online_entries(self):
        &quot;&quot;&quot;
        Returns entries in this category with status of &quot;online&quot;.
        Access this through the property ``online_entry_set``.
        &quot;&quot;&quot;
        from website.apps.blog.models import Entry
        return self.entry_set.filter(status=Entry.STATUS_ONLINE)

    online_entry_set = property(_get_online_entries)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voici la nouvelle version du template :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{% extends &quot;layout/base.html&quot; %}
{% block title %}{{ object.name }}{% endblock title %}
{% block content %}
&lt;h2&gt;{{ object.name }}&lt;/h2&gt;
&lt;ul&gt;
    {% for entry in object.online_entry_set.all %}
    &lt;li&gt;
        &lt;a href=&quot;{{ entry.get_absolute_url }}&quot;&gt;{{ entry.title }}&lt;/a&gt; |
        &lt;small&gt;
        {{ entry.publication_date|date:&quot;Y/m/d @ H:i:s&quot; }} -
        &lt;a href=&quot;{{ entry.category.get_absolute_url }}&quot;&gt;{{ entry.category.name }}&lt;/a&gt;
        &lt;/small&gt;
    &lt;/li&gt;
    {% endfor %}
&lt;/ul&gt;
{% endblock content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nos templates sont en place. C’est très rustre mais c’est un exemple
d’application.&lt;/p&gt;
&lt;p&gt;Qu’avons-nous oublié ? Mais oui, bien sûr, les fils RSS ! Un blog sans fils
RSS n’est pas un blog. C’est indispensable. Heureusement, Django nous permet
d’ajouter cette fonctionnalité en moins de deux minutes. Voyons comment procéder.&lt;/p&gt;
&lt;h2 id=&quot;ajout-des-fils-rss&quot;&gt;Ajout des fils RSS&lt;/h2&gt;
&lt;p&gt;Django embarque une application pour la génération de fils RSS. N’hésitez pas
à consulter la section &lt;a href=&quot;http://docs.djangoproject.com/en/dev/ref/contrib/syndication/#ref-contrib-syndication&quot;&gt;The syndication feed framework&lt;/a&gt;
de la documentation pour en savoir plus. Difficile de faire plus simple et efficace.&lt;/p&gt;
&lt;p&gt;Commençons par écrire nos tests. Ajoutons les méthodes &lt;code&gt;test_rss_entries&lt;/code&gt; et
&lt;code&gt;test_rss_category&lt;/code&gt; à notre module &lt;code&gt;test.py&lt;/code&gt;, placé dans le répertoire de
l’application :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;def test_rss_entries(self):
    &quot;&quot;&quot;
    Tests entries RSS feed.
    &quot;&quot;&quot;
    blog_url = reverse(&#39;blog&#39;)
    url = u&#39;%sfeed/rss/entries/&#39; % blog_url
    response = self.client.get(url)
    self.failUnlessEqual(response.status_code, 200)

def test_rss_category(self):
    &quot;&quot;&quot;
    Tests categories RSS feed.
    &quot;&quot;&quot;
    from website.apps.blog.models import Category
    categories = Category.online_objects.all()
    blog_url = reverse(&#39;blog&#39;)
    for category in categories:
        url = u&#39;%sfeed/rss/category/%s/&#39; % (blog_url, category.slug)
        response = self.client.get(url)
        self.failUnlessEqual(response.status_code, 200)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans notre application &lt;code&gt;blog&lt;/code&gt;, créons un module &lt;code&gt;feeds.py&lt;/code&gt;. Dans ce module,
plaçons nos classes de syndication :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;# -*- coding: utf-8
&quot;&quot;&quot;
Feeds of ``blog`` application.
&quot;&quot;&quot;
from django.utils.feedgenerator import Rss201rev2Feed
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.contrib.syndication import feeds
from django.contrib.sites.models import Site

from website.apps.blog.models import Entry
from website.apps.blog.models import Category

class RssEntries(feeds.Feed):
    &quot;&quot;&quot;
    RSS entries.
    &quot;&quot;&quot;
    feed_type = Rss201rev2Feed
    title_template = &quot;blog/feeds/entry_title.html&quot;
    description_template = &quot;blog/feeds/entry_description.html&quot;

    def title(self):
        &quot;&quot;&quot;
        Channel title.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;%(site_name)s: RSS entries&#39;) % {
            &#39;site_name&#39;: site.name,
        }

    def description(self):
        &quot;&quot;&quot;
        Channel description.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;RSS feed of recent entries posted on %(site_name)s.&#39;) % {
            &#39;site_name&#39;: site.name,
        }

    def link(self):
        &quot;&quot;&quot;
        Channel link.
        &quot;&quot;&quot;
        return reverse(&#39;blog&#39;)

    def items(self):
        &quot;&quot;&quot;
        Channel items.
        &quot;&quot;&quot;
        return Entry.online_objects.order_by(&#39;-publication_date&#39;)[:10]

    def item_pubdate(self, item):
        &quot;&quot;&quot;
        Channel item publication date.
        &quot;&quot;&quot;
        return item.publication_date

class RssCategory(RssEntries):
    &quot;&quot;&quot;
    RSS category.
    &quot;&quot;&quot;
    def title(self, obj):
        &quot;&quot;&quot;
        Channel title.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;%(site_name)s: RSS %(category)s category&#39;) % {
            &#39;site_name&#39;: site.name,
            &#39;category&#39;: obj.name,
        }

    def description(self, obj):
        &quot;&quot;&quot;
        Channel description.
        &quot;&quot;&quot;
        site = Site.objects.get_current()
        return _(&#39;RSS feed of recent entries posted in the category %(category)s on %(site_name)s.&#39;) % {
            &#39;category&#39;: obj.name,
            &#39;site_name&#39;: site.name,
        }

    def link(self, obj):
        &quot;&quot;&quot;
        Channel link.
        &quot;&quot;&quot;
        return reverse(&#39;blog_category&#39;, args=[obj.slug])

    def get_object(self, bits):
        &quot;&quot;&quot;
        Object: the Category.
        &quot;&quot;&quot;
        if len(bits) != 1:
            raise ObjectDoesNotExist
        return Category.online_objects.get(slug=bits[0])

    def items(self, obj):
        &quot;&quot;&quot;
        Channel items.
        &quot;&quot;&quot;
        return obj.online_entry_set

    def item_pubdate(self, item):
        &quot;&quot;&quot;
        Channel item publication date.
        &quot;&quot;&quot;
        return item.publication_date
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dans le module &lt;code&gt;urls.py&lt;/code&gt; de l’application &lt;code&gt;blog&lt;/code&gt;, ajoutons le support des
fils RSS :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&quot;&quot;&quot;
URLs of blog application.
&quot;&quot;&quot;
from django.conf.urls.defaults import *

from website.apps.blog.models import Entry
from website.apps.blog.models import Category

from website.apps.blog.feeds import RssEntries
from website.apps.blog.feeds import RssCategory

rss_feeds = {
    &#39;entries&#39;: RssEntries,
    &#39;category&#39;: RssCategory,
}

urlpatterns = patterns(&#39;&#39;,
    url(r&#39;^feed/rss/(?P&lt;url&gt;.*)/$&#39;,
        &#39;django.contrib.syndication.views.feed&#39;, {
            &#39;feed_dict&#39;: rss_feeds,
        },
        name=&#39;blog_rss_feed&#39;,
    ),
    ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Il ne reste plus qu’à créer les templates :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir templates/blog/feeds
$ touch templates/blog/feeds/entry_title.html
$ touch templates/blog/feeds/entry_description.html
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le template &lt;code&gt;templates/blog/feeds/entry_title.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{{ obj.title }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Le template &lt;code&gt;templates/blog/feeds/entry_description.html&lt;/code&gt; :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;lang-html+django&quot;&gt;{{ obj.body|safe }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lançons les tests pour vérifier si tout est OK:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python manage.py test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Vous ne devriez pas rencontrer d’erreur.&lt;/p&gt;
&lt;p&gt;Lancez le serveur:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans votre navigateur, allez à ces adresses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://127.0.0.1:8000/feed/rss/entries/&quot;&gt;http://127.0.0.1:8000/feed/rss/entries/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://127.0.0.1:8000/feed/rss/category/test/&quot;&gt;http://127.0.0.1:8000/feed/rss/category/test/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vous devriez voir les fils. Merci Django !&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Cet exemple d’application Django est un blog simpliste, basique, lambda. Moult
fonctionnalités ont été volontairement omises (formatage du contenu des billets
avec une syntaxe wiki, gestion multi-catégories, support des tags, support du
format Atom pour les fils de syndication, amélioration de l’interface
d’administration, amélioration des templates à l’aide d’includes, support des
commentaires, ajout d’une sidebar… ) pour ne pas transformer ce tutorial en
ouvrage technique. La documentation de Django est complète et claire. N’hésitez
pas à la consulter au moindre problème.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://github.com/gillesfabio/creer-un-blog-avec-django&quot;&gt;Télécharger&lt;/a&gt; le source
de l’application.&lt;/p&gt;

    </content>
    
    <category term="python" />
    
    <category term="django" />
    
    <category term="development" />
    
  </entry>
  
</feed>
