=======================================
Les tutoriels et articles v2.0 (ZEP 12)
=======================================

Vocabulaire et définitions
==========================

- **Contenu** (*content*): désigne, de manière générale, tout ce qui peut être produit et édité sur Zeste de Savoir, c'est-à-dire, à l'heure actuelle, un article ou un tutoriel. Tout contenu est rattaché à un dossier qui lui est propre et dont l'organisation est explicitée plus bas. Ce dossier comporte des informations sur le contenu lui-même (*metadata* : un auteur, une description, une licence...) ainsi que des textes, agencés dans une arborescence bien précise.
- **Article** : contenu, généralement court, visant à faire découvrir un sujet plus qu'à l'expliquer au lecteur (introduit sans rentrer dans les détails) ou à fournir un état des lieux sur un point donné de manière concise (rapports de *release*, actualité...).
- **Tutoriel** : contenu, en général plus long, ayant pour objectif d'enseigner un savoir-faire au lecteur.
- **git**: système de gestion de versions employé (entre autres) par ZdS. Il permet de faire coexister différentes versions d'un contenu de manière simple et transparente pour l'auteur.
- **Version** : état du contenu à un moment donné. Toute mise à jour du contenu (ou d'une de ses composantes) génère une nouvelle version de ce dernier, laquelle est désignée par un *hash*, c'est-à-dire par une chaîne de 20 caractères de long (aussi appelée *sha*, en référence à l'algorithme employé pour les générer). Ce *hash* permet d'identifier de manière unique cette version parmi toutes celles du contenu. Certaines versions, en plus du *sha*, sont désignées par un nom. On distingue ainsi la version brouillon (*draft*), la version en bêta (*beta*), la version en validation (*validation*) et la version publiée (*public*). Pour ce faire, les *hash* correspondant à ces versions sont simplement mis de côté.
- Fichier **manifest.json** : fichier à la racine de tout contenu dont l'objectif est de décrire ce dernier. Un tel fichier comporte deux types d'informations : à propos du contenu en lui-même (les métadonnées mentionnées plus haut) et à propos de son arborescence. La spécification de ce fichier est détaillée plus loin. On retiendra qu'à chaque version correspond un fichier ``manifest.json`` et que le contenu de ce dernier peut fortement varier d'une version à l'autre.
- **Conteneur** (*container*) : sous-structure d'un contenu. Explicité plus bas.
- **Extrait** (*extract*) : base atomique (plus petite unité) d'un contenu. Explicité plus bas.

De la structure générale d'un contenu
=====================================

Des extraits
------------

Un **extrait** est une unité de texte. Il possède un titre (*title*) et du 
texte (*text*). Dans l'interface d'édition d'un tutoriel, un extrait est 
désigné par le terme « section ».

Des conteneurs
--------------

Un conteneur est une boîte ayant pour rôle de regrouper des éléments 
sémantiquement proches. Il est caractérisé par son titre (*title*) et possède 
une introduction (*introduction*) ainsi qu'une conclusion (*conclusion*), 
possiblement vides. 

Les éléments regroupés, appelés « enfants » (*children*) peuvent être de deux 
types : conteneur ou extrait. La structure d'un conteneur obéit à certaines 
règles :

* Un conteneur ne peut comporter un conteneur composé lui-même d'un conteneur ;
* Un conteneur ne peut comporter d'enfants directs à la fois des conteneurs et des extraits.

Au niveau de la terminologie, on désigne par « partie » tout conteneur de 
niveau 1, c'est-à-dire n'étant pas inclus dans un autre conteneur, et par 
« chapitre » tout conteneur enfant d'une partie.

Un contenu
----------

Un **contenu** est un agencement particulier de conteneurs et d'extraits. Il 
est décrit par des métadonnées (*metadata*), détaillées 
`ici <./contents_manifest.html>`__. Une de ces métadonnées est le type : article 
ou tutoriel. Leur visée pédagogique diffère, mais aussi leur structure : un 
article ne peut comporter de conteneurs, seulement des extraits, ce qui n'est 
pas le cas d'un tutoriel.

Les exemples suivants devraient éclairer ces notions.

Communément appelé « mini-tutoriel » :

.. sourcecode:: none

    + Tutoriel
        + Section 
        + Section
        + Section

Communément appelé « moyen-tutoriel » :

.. sourcecode:: none

    + Tutoriel
        + Partie
            + Section
        + Partie 
            + Section
            + Section 

Communément appelé « big-tutoriel » :

.. sourcecode:: none

    + Tutoriel
        + Partie
            + Chapitre
                + Section 
                + Section
            + Chapitre
                + Section 
        + Partie
            + Chapitre
                + Section
                + Section

On peut aussi faire un mélange des conteneurs :

.. sourcecode:: none

    + Tutoriel
        + Partie
            + Section
            + Section
        + Partie
            + Chapitre
                + Section
            + Chapitre
                + Section 

Mais pas de conteneurs et d'extraits adjacents :

.. sourcecode:: none

    /!\ Invalide !

    + Tutoriel
        + Partie
            + Section
        + Section /!\ Impossible !
        + Partie 
            + Chapitre
                + Section
            + Section /!\ Impossible !

Pour finir, un article. Même structure qu'un mini-tutoriel, mais vocation 
pédagogique différente :

.. sourcecode:: none

    + Article
        + Section 
        + Section

D'autre part, tout contenu se voit attribuer un identifiant unique sous la 
forme d'un entier naturel (en anglais : *pk*, pour *primary key*). Cet 
identifiant apparaît dans les URLs, qui sont de la forme 
``/contenus/{pk}/{slug}``. Il rend plus efficace la recherche en base de 
données. Le *slug*, quant à lui, a le mérite d'être compréhensible par un être 
humain et permet de gérer les cas de redirection 301 (voir plus bas). 

Des objets en général
---------------------

Tous les textes (introductions, conclusions et extraits) sont formatés en 
Markdown (dans la version étendue de ZdS).

Conteneurs et extraits sont des **objets** (*object*). Dès lors, ils possèdent 
tous deux un *slug* (littéralement, « limace ») : il s'agit d'une chaîne de 
caractères générée à partir du titre de l'objet et qui, tout en restant lisible 
par un être humain, le simplifie considérablement. Un *slug* est uniquement 
composé de caractères alphanumériques minuscules et non-accentués 
(``[a-z0-9]*``) ainsi que des caractères ``-`` (tiret) et ``_`` (*underscore*). 
Ce *slug* a deux utilités : il est employé dans l'URL permettant de consulter 
l'objet depuis le site Web et dans le nom des fichiers ou dossiers employés pour le 
stocker (détails plus bas). Dès lors, cette spécification **impose** que ce 
*slug* soit unique au sein du conteneur parent, et que le *slug* du contenu 
soit unique au sein de tous les contenus de ZdS.

.. note::

    À noter que l'*underscore* est conservé par compatibilité avec l'ancien 
    système, les nouveaux *slugs* générés par le système d'édition de ZdS 
    n'en contiendront pas.

.. note::

    Lors du déplacement d'un conteneur ou d'un extrait, les *slugs* sont modifiés 
    de manière à ce qu'il n'y ait pas de collision.

.. attention::

    L'introduction et la conclusion d'un conteneur possèdent également un 
    *slug*, pour des raisons de stockage (voir plus bas). Il ne faut pas 
    oublier la contrainte d'unicité à l'intérieur d'un conteneur.

Cycle de vie des contenus
=========================

Quelque soit le type de contenu, le cycle de vie de celui-ci reste toujours le même.
Un contenu peut être rédigé par un ou plusieurs auteurs. Chaque modification 
est conservée afin de pouvoir retrouver l'historique des modifications et éventuellement
récupérer un morceau de texte perdu. Lorsqu'un contenu est créé il rentre dans 
sa première étape.

Le brouillon
------------

Le brouillon est la première étape du cycle de vie d'un contenu. Il donne 
toujours l'état le plus récent d'un contenu vu par les auteurs. Chaque fois 
que le contenu est modifié, c'est la version brouillon qui est mise à jour. 
La version brouillon est accessible uniquement pour les auteurs et validateurs 
d'un tutoriel. Si on souhaite donner un accès en lecture seule à nos écrits, 
il faut passer par la méthode adéquate.

La bêta
-------

Lorsque les auteurs estiment que leur tutoriel a atteint un certain niveau de 
maturité, et qu'ils souhaitent recueillir des retours de la communauté, ils ont 
la possibilité de le mettre à la disposition de cette dernière le contenu en
lecture seule. C'est le mode bêta.

Lors de la mise en bêta d'un contenu, un sujet est automatiquement ouvert dans 
la Bêta-zone, contenant l'adresse de la bêta. Cette dernière est de la forme : 
``/contenus/beta/{pk}/{slug}/``.

Il faut en outre noter que seule une version précise du contenu est mise en 
bêta. Au moment de la mise en bêta, les versions brouillon et bêta coïncident 
mais l'auteur peut tout à fait poursuivre son travail sans affecter la seconde. 
Seulement, la version brouillon ne sera plus identique à la version en bêta et 
il ne faudra pas oublier de mettre à jour cette dernière pour que la communauté 
puisse juger des dernières modifications.

La validation
-------------

Une fois que l'auteur a eu assez de retours sur son contenu, et qu'il estime 
qu'il est prêt à être publié, il décide d'envoyer son contenu en validation. 
*Via* l'interface idoine, un validateur peut alors réserver le contenu et 
commencer à vérifier qu'il satisfait la politique éditoriale du site. Dans le 
cas contraire, le contenu est rejeté et un message est envoyé aux auteurs pour 
expliquer les raisons du refus.

L'envoi en validation n'est pas définitif, dans le sens où vous pouvez à tout 
moment mettre à jour la version en cours de validation. Évitez d'en abuser tout 
de même, car, si un validateur commence à lire votre tutoriel, il devra 
recommencer son travail si vous faites une mise à jour dessus. Cela pourrait non 
seulement ralentir le processus de validation de votre tutoriel, mais aussi ceux 
autres tutoriels !

Comme pour la bêta, la version brouillon du tutoriel peut continuer à être 
améliorée pendant que la version de validation reste figée. Auteurs et validateurs 
peuvent donc continuer à travailler chacun de leur côté.

La publication
--------------

Une fois que le contenu est passé en validation et a satisfait les critères 
éditoriaux, il est publié. Il faut bien préciser que le processus de 
validation peut être assez long. De plus, un historique de validation est 
disponible pour les validateurs.

La publication d'un contenu entraîne l'exportation du contenu en plusieurs formats :

- Markdown : disponible uniquement pour les membres du staff et les auteurs du contenu
- HTML
- PDF
- EPUB : format de lecture adapté aux liseuses
- Archive : un export de l'archive contenant la version publiée du contenu

Pour différentes raisons, il se peut que l'export dans divers formats échoue. 
Dans ce cas, le lien de téléchargement n'est pas présenté. Un fichier de log 
sur le serveur enregistre les problèmes liés à l'export d'un format.

Aujourd'hui, il existe des bugs dans la conversion en PDF (notamment les blocs spécifiques à ZdS), 
qui devraient être réglés plus tard avec la 
`ZEP-05 <http://zestedesavoir.com/forums/sujet/676/zep-05-refonte-du-traitement-markdown-pour-lexport>`__)

Enfin, signalons qu'il est possible à tout moment pour un membre de l'équipe 
de dépublier un contenu. Le cas échéant, un message sera envoyé aux auteurs, 
indiquant les raisons de la dépublication.

L'entraide
----------

Afin d'aider les auteurs de contenus à rédiger ces derniers, des options lors 
de la création/édition de ce dernier sont disponibles. L'auteur peut ainsi 
faire aisément une demande d'aide pour les raisons suivantes 
(liste non exhaustive) :

- Besoin d'aide à l'écriture
- Besoin d'aide à la correction/relecture
- Besoin d'aide pour illustrer
- Désir d'abandonner le contenu et recherche d'un repreneur

L'ensemble des contenus à la recherche d'aide est visible via la page 
``/contenus/aides/``. Cette page génère un tableau récapitulatif de toutes les 
demandes d'aides pour les différents contenus et des filtres peuvent être 
appliqués.

Il est également possible **pour tout membre qui n'est pas auteur du contenu consulté** 
de signaler une erreur, en employant le bouton prévu à cet effet et situé en 
bas d'une page du contenu.


   .. figure:: ../images/tutorial/warn-typo-button.png
      :align: center

      Bouton permentant de signaler une erreur

Ce bouton est disponible sur la version publiée ou en bêta d'un contenu. Cliquer sur celui-ci ouvre une boite de dialogue :

   .. figure:: ../images/tutorial/warn-typo-dial.png
      :align: center

      Boite de dialogue permettant de signaler à l'auteur une erreur qu'il aurait commise

Le message ne peut pas être vide, mais n'hésitez pas à être précis et à fournir 
des détails. Cliquer sur "Envoyer" enverra un message privé aux auteurs du 
contenu, reprenant votre message sous forme d'une citation. Vous participerez 
également à la conversation, afin que les auteurs puissent vous demander plus 
de détails si nécessaire.

Import de contenus
==================

Zeste de Savoir permet d'importer des contenus provenant de sources 
extérieures.


Ce système est utilisable pour créer de nouveaux contenus à partir de zéros,
ou bien si vous avez téléchargé l'archive correspondante à votre contenu, modifiée et
que vous souhaitez importer les modifications.

Il suffit de faire une archive zip du répertoire
dans lequel se trouvent les fichiers de votre contenu, puis de vous rendre soit sur
"Importer un nouveau contenu", soit sur "Importer une nouvelle version" dans n'importe quel contenu
et de renseigner les champs relatifs à l'import d'une archive, puis de cliquer sur "Importer".

    .. figure:: ../images/tutorial/import-archive.png
       :align:   center

       Exemple de formulaire d'importation : mise à jour d'un contenu

Import d'image
--------------

À noter que si vous souhaitez importer des images de manière à ce qu'elles soient
directement intégrée à votre contenu, vous devez écrire les liens vers cette image sous la
forme ``![légende](archive:image.extension)``, puis créer une archive contenant toutes celles-ci.
Le système se chargera alors d'importer les images dans la gallerie correspondante, puis de remplacer
les liens quand c'est nécessaire. Ainsi,

.. sourcecode:: text

    Voici ma belle image : ![Mon image](archive:image.png)

Sera remplacé en

.. sourcecode:: text

    Voici ma belle image : ![Mon image](/media/galleries/xx/yyyyyy.png)

À condition que ``image.png`` soit présent dans l'archive (à sa racine) et soit une image valide.

Règles
------

Au maximum, le système d'importation tentera d'être compréhensif envers une arborescence qui
différente de celle énoncée ci-dessus. Par contre 
**l'importation réorganisera les fichiers importés de la manière décrite ci-dessus**, 
afin de parer aux mauvaises surprises.

Tout contenu qui ne correspond pas aux règles précisées ci-dessus ne sera pas 
ré-importable. Ne sera pas ré-importable non plus tout contenu dont les 
fichiers indiqués dans le ``manifest.json`` n'existent pas ou sont incorrects. 
Seront supprimés les fichiers qui seraient inutiles (images, qui actuellement 
doivent être importées séparément dans une galerie, autres fichiers 
supplémentaires) pour des raisons élémentaires de sécurité.

Aspects techniques et fonctionnels
==================================

Les métadonnées
---------------

On distingue actuellement deux types de métadonnées (*metadata*) : celles 
versionnées (et donc reprises dans le ``manifest.json``) et celles qui ne le 
sont pas. La liste exhaustive de ces dernières (à l'heure actuelle) est la 
suivante :

+ Les *hash* des différentes versions du tutoriel (``sha_draft``, ``sha_beta``, ``sha_public`` et ``sha_validation``) ;
+ Les auteurs du contenu ;
+ Les catégories auxquelles appartient le contenu ;
+ La miniature ;
+ L'origine du contenu, s'il n'a pas été créé sur ZdS mais importé avec une licence compatible ;
+ L'utilisation ou pas de JSFiddle dans le contenu ;
+ Différentes informations temporelles : date de création (``creation_date``), de publication (``pubdate``) et de dernière modification (``update_date``)
+ La galerie ;
+ Le sujet de la bêta, s'il existe.

Le stockage en base de données
------------------------------

Les métadonnées non versionnées sont stockées dans la base de données, à l'aide 
du modèle ``PublishableContent``. Pour des raisons de facilité, certaines des 
métadonnées versionnées sont également intégrées dans la base : 

+ Le titre 
+ Le type de contenu 
+ La licence 
+ La description 

En ce qui concerne cette dernière, celle stockée en base est **toujours** 
celle de **la version brouillon**. Il ne faut donc **en aucun cas** les 
employer pour résoudre une URL ou à travers une template correspondant 
à la version publiée.

Les métadonnées versionnées sont stockées dans le fichier ``manifest.json``. Ce 
dernier est rattaché à une version du contenu par le truchement de git.

À la publication du contenu, un objet ``PublishedContent`` est créé, reprenant 
les informations importantes de cette version. C'est alors cet objet qui est 
utilisé pour résoudre les URLs. C'est également lui qui se cache derrière le 
mécanisme de redirection si, entre deux versions, le *slug* du contenu change.

Le stockage *via* des dossiers
------------------------------

Comme énoncé plus haut, chaque contenu possède un dossier qui lui est propre 
(dont le nom est le *slug* du contenu), stocké dans l'endroit défini par la 
variable ``ZDS_APP['content']['repo_path']``. Dans ce dossier se trouve le 
fichier ``manifest.json``.

Pour chaque conteneur, un dossier est créé, contenant les éventuels fichiers 
correspondant aux introduction, conclusion et différents extraits, ainsi que 
des dossiers pour les éventuels conteneurs enfants. Il s'agit de la forme d'un 
contenu tel que généré par ZdS en utilisant l'éditeur en ligne.

Il est demandé de se conformer au maximum à cette structure pour éviter les 
mauvaises surprises en cas d'édition externe (voir ci-dessous).

Les permissions
---------------

Afin de gérer ce module, trois permissions peuvent être utilisées :

- ``tutorialv2.change_publishablecontent`` : pour le droit d'accéder et de modifier les contenus même sans en être l'auteur ;
- ``tutorialv2.change_validation`` : pour le droit à accéder à l'interface de validation, réserver, valider ou refuser des contenus ;
- ``tutorialv2.change_contentreaction`` : pour le droit à modérer les commentaires sur les contenus une fois publiés (masquer, éditer, ...).

Ces permissions doivent être accordées au administateurs/modérateurs/validateurs selon les besoins via l'interface d'administration de Django.

Processus de publication
------------------------

Apès avoir passé les étapes de validation, le contenu est près à être publié. 
Cette action est effectuée par un membre du Staff. Le but de la publication 
est double : permettre aux visiteurs de consulter le contenu, mais aussi 
d’effectuer certains traitements (détaillés ci-après) afin que celui-ci soit 
sous une forme qui soit plus rapidement affichable par ZdS. C’est pourquoi ces 
contenus ne sont pas stockés au même endroit (voir 
``ZDS_AP['content']['repo_public_path']``) que les brouillons.

La publication se passe comme suit :

1. Un dossier temporaire est créé, afin de ne pas affecter la version publique précédente, si elle existe. Ce dossier est nommé ``{slug}__build``;
2. Le code *markdown* est converti en HTML afin de gagner du temps à l'affichage. Pour chaque conteneur, deux cas se présentent : 
    * Si celui-ci contient des extraits, ils sont tous rassemblés dans un seul fichier HTML, avec l'introduction et la conclusion ;
    * Dans le cas contraire, l'introduction et la conclusion sont placées dans des fichiers séparés, et les champs correspondants dans le *manifest* sont mis à jour.
3. Le *manifest* correspondant à la version de validation est copié. Il sera nécessaire afin de valider les URLs et générer le sommaire. Néanmoins, les informations inutiles sont enlevées (champ ``text`` des extraits, champs ``introduction`` et ``conclusion`` des conteneurs comportant des extraits), une fois encore pour gagner du temps ;
4. L'exportation vers les autres formats est ensuite effectué (PDF, EPUB, ...) en utilisant `pandoc (en) <http://pandoc.org/>`__. Cette étape peut être longue si le contenu possède une taille importante. Il est également important de mentionner que pendant cette étape, l'ensemble des images qu'utilise le contenu est récupéré et que si ce n'est pas possible, une image par défaut est employée à la place, afin d'éviter les erreurs ;
5. Finalement, si toutes les étapes précédentes se sont bien déroulées, le dossier temporaire est déplacé à la place de celui de l'ancienne version publiée. Un objet ``PublishedContent`` est alors créé (ou mis à jour si le contenu avait déjà été publié par le passé), contenant les informations nécessaire à l'affichage dans la liste des contenus publiés. Le ``sha_public`` est mis à jour dans la base de données et l'objet ``Validation`` est changé de même.

Consultation d'un contenu publié
--------------------------------

On n'utilise pas git pour afficher la version publiée d'un contenu. Dès lors, 
deux cas se présentent :

+ L'utilisateur consulte un conteneur dont les enfants sont eux-mêmes des conteneurs (c'est-à-dire le conteneur principal ou une partie d'un big-tutoriel) : le ``manifest.json`` est employé pour générer le sommaire, comme c'est le cas actuellement. L'introduction et la conclusion sont également affichées.
+ L'utilisateur consulte un conteneur dont les enfants sont des extraits : le fichier HTML généré durant la publication est employé tel quel par le gabarit correspondant, additionné de l'éventuelle possibilité de faire suivant/précédent (qui nécessite la lecture du ``manifest.json``).

Qu'en est-il des images ?
-------------------------

Le versionnage des images d'un contenu (celles qui font partie de la galerie 
rattachée) continue à faire débat, et il a été décidé pour le moment de ne 
pas les versionner, pour des raisons simples :

- Versionner les images peut rendre très rapidement une archive lourde : si l'auteur change beaucoup d'images, il va se retrouver avec des images plus jamais utilisées qui traînent dans son archive ;
- Avoir besoin d'interroger le dépôt à chaque fois pour lire les images peut rapidement devenir lourd pour la lecture.

Le parti a été pris de ne pas versionner les images qui sont stockées sur le 
serveur. Ce n'est pas critique et on peut très bien travailler ainsi. Par 
contre, il vaudra mieux y réfléchir pour une version 3 afin de proposer 
une rédaction totale en mode hors-ligne.

Passage des tutos v1 aux tutos v2
=================================

Le parseur v2 ne permettant qu'un support minimal des tutoriels à l'ancien 
format, il est nécessaire de mettre en place des procédures de migration.

Migrer une archive v1 vers une archive v2
-----------------------------------------

Le premier cas qu'il est possible de rencontrer est la présence d'une 
archive *hors ligne* d'un tutoriel à la version 1.

La migration de cette archive consistera alors à ne migrer que le *manifest*.
En effet, la nouvelle architecture étant bien plus souple du 
point de vue des nomenclatures, il ne sera pas nécessaire de l'adapter.

Un outil intégré au code de ZdS a été mis en place. Il vous faudra alors :

- Décompresser l'archive ;
- Exécuter ``python manage.py upgrade_manifest_to_v2 /chemin/vers/archive/decompressee/manifest.json`` ;
- Recompresser l'archive.

Si vous souhaitez implémenter votre propre convertisseur, voici l'algorithme utilisé en Python :

.. sourcecode:: python

    with open(_file, "r") as json_file:
        data = json_reader.load(json_file)
    _type = "TUTORIAL"
    if "type" not in data:
        _type = "ARTICLE"
    versioned = VersionedContent("", _type, data["title"], slugify(data["title"]))
    versioned.description = data["description"]
    versioned.introduction = data["introduction"]
    versioned.conclusion = data["conclusion"]
    versioned.licence = Licence.objects.filter(code=data["licence"]).first()
    versioned.version = "2.0"
    versioned.slug = slugify(data["title"])
    if "parts" in data:
        # if it is a big tutorial
        for part in data["parts"]:
            current_part = Container(part["title"],
                str(part["pk"]) + "_" + slugify(part["title"]))
            current_part.introduction = part["introduction"]
            current_part.conclusion = part["conclusion"]
            versioned.add_container(current_part)
            for chapter in part["chapters"]:
                current_chapter = Container(chapter["title"],
                    str(chapter["pk"]) + "_" + slugify(chapter["title"]))
                current_chapter.introduction = chapter["introduction"]
                current_chapter.conclusion = chapter["conclusion"]
                current_part.add_container(current_chapter)
                for extract in chapter["extracts"]:
                    current_extract = Extract(extract["title"],
                        str(extract["pk"]) + "_" + slugify(extract["title"]))
                    current_chapter.add_extract(current_extract)
                    current_extract.text = current_extract.get_path(True)
                    
    elif "chapter" in data:
        # if it is a mini tutorial
        for extract in data["chapter"]["extracts"]:
            current_extract = Extract(extract["title"],
                str(extract["pk"]) + "_" + slugify(extract["title"]))
            current_extract.text = current_extract.get_path(True)
            versioned.add_extract(current_extract)
    elif versioned.type == "ARTICLE":
        extract = Extract(data["title"], "text")
        versioned.add_extract(extract)

Migrer la base de données
-------------------------

Si vous faites tourner une instance du code de Zeste de Savoir sous la version 1.X et que vous passez à la v2.X, vous allez
devoir migrer les différents tutoriels. Pour cela, il faudra simplement exécuter la commande ``python manage.py migrate_to_zep12.py``.
