diff --git a/.gitignore b/.gitignore index 6ae27f46fe..eb86f42976 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ base.db /static /tutoriels-private /tutoriels-public +/contents-private +/contents-public /media /articles-data @@ -13,6 +15,8 @@ base.db /tutoriels-public-test /media-test /articles-data-test +/contents-private-test +/contents-public-test /apache-solr diff --git a/.travis.yml b/.travis.yml index 338e30c185..00fb9d93a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,11 @@ env: global: - secure: "azmDZZQZzf88zpbkYpLpxI66vpEVyv+kniW0QdWAt4qlys8P5OcO3VJBR5YT85vlvnjN9b6raWQAL1ymee0WmVdTmzXed8XjZv7t9QXVw7pfezxMKlEftVp/4Cu4wtvbew0ViZXNWV2hNXHzEqlhgnoIOq94i0UzZ7grMrI0xm0=" matrix: - - TEST_APP="-e back_mysql" + - TEST_APP="-e back_mysql -- zds.member zds.mp zds.utils zds.forum zds.gallery zds.pages zds.search zds.featured" + - TEST_APP="-e back_mysql -- zds.article zds.tutorial zds.tutorialv2" - TEST_APP="-e front" + notifications: irc: channels: diff --git a/assets/js/accordeon.js b/assets/js/accordeon.js index a905de46db..91bb9b760b 100644 --- a/assets/js/accordeon.js +++ b/assets/js/accordeon.js @@ -9,8 +9,10 @@ function accordeon($elem){ $("h4 + ul, h4 + ol", $elem).each(function(){ - if($(".current", $(this)).length === 0) - $(this).hide(); + if(!$(this).hasClass("unfolded")){ + if($(".current", $(this)).length === 0) + $(this).hide(); + } }); $("h4", $elem).click(function(e){ diff --git a/assets/js/compare-commits.js b/assets/js/compare-commits.js new file mode 100644 index 0000000000..e36601f2b0 --- /dev/null +++ b/assets/js/compare-commits.js @@ -0,0 +1,43 @@ +/* + * Allow the user to compare two commits + */ + +(function(document, $, undefined){ + "use strict"; + + function toogleRadioInput($radioInput){ + var $row = $radioInput.parent().parent(); + + if($radioInput.attr("name") === "compare-from") { + $row.prevAll().find("[name='compare-to']").prop("disabled", false); + $row.nextAll().find("[name='compare-to']").prop("disabled", true); + $row.find("[name='compare-to']").prop("disabled", true); + } + else { + $row.prevAll().find("[name='compare-from']").prop("disabled", true); + $row.nextAll().find("[name='compare-from']").prop("disabled", false); + $row.find("[name='compare-from']").prop("disabled", true); + } + } + + $(".commits-list input[name^='compare']").on("change", function(){ + toogleRadioInput($(this)); + }); + + $(document).ready(function(){ + $(".commits-list input[name^='compare']:checked").each(function(){ + toogleRadioInput($(this)); + }); + }); + + $(".commits-compare-form").on("submit", function(){ + var $form = $(this), + $fromInput = $form.find("input[name='from']"), + $toInput = $form.find("input[name='to']"), + compareFrom = $(".commits-list input[name='compare-from']:checked").val(), + compareTo = $(".commits-list input[name='compare-to']:checked").val(); + + $fromInput.val(compareFrom); + $toInput.val(compareTo); + }); +})(document, jQuery); diff --git a/assets/scss/base/_tables.scss b/assets/scss/base/_tables.scss index 7cf367d4df..fb6fe742f8 100644 --- a/assets/scss/base/_tables.scss +++ b/assets/scss/base/_tables.scss @@ -37,25 +37,49 @@ table { &.fullwidth { width: 100%; } +} - &.diff { - tbody tr { - border-bottom: none; - font-family : $font-monospace; - background: #FFF; +/* Specific for diff */ + +.diff_delta { + overflow-x: auto; + width: 100%; + margin: 15px 0; /* add margin to container */ + + table.diff { + font-family: $font-monospace; + font-size: 0.9em; + border: 2px solid gray; + margin: 0; /* abort table default margin */ + + tr { line-height: 1em; - .diff_next { - display: none; - } - td.diff_header { - padding : 5px; - } + border-bottom: none; + } + + .diff_header { + background-color: #e0e0e0; + padding: 5px; + } + + td.diff_header { + text-align: right; + } + + .diff_next { + display: none; } } } -.diff_delta { - overflow: scroll; - width: 100%; - font-size : 0.9em; -} \ No newline at end of file +.diff_add { /* added text */ + background-color: #aaffaa; +} + +.diff_chg { /* changed text */ + background-color: #fff8ab; +} + +.diff_sub { /* deleted text */ + background-color: #ffaaaa; +} diff --git a/assets/scss/layout/_main.scss b/assets/scss/layout/_main.scss index 489114c571..aa8f409a88 100644 --- a/assets/scss/layout/_main.scss +++ b/assets/scss/layout/_main.scss @@ -118,6 +118,10 @@ margin-top: 0; } +.pagination-bottom-clear{ + clear: both; +} + @media only screen and #{$media-mega-wide} { .main .content-container { .content-wrapper { diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 3a25ab8d0a..95b5af1b8e 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -80,6 +80,7 @@ @import "pages/gallery"; @import "pages/api"; @import "pages/tutorial-help"; +@import "pages/tutorial-history"; /*------------------------- 10. High pixel ratio (retina) diff --git a/assets/scss/pages/_tutorial-history.scss b/assets/scss/pages/_tutorial-history.scss new file mode 100644 index 0000000000..e803397d34 --- /dev/null +++ b/assets/scss/pages/_tutorial-history.scss @@ -0,0 +1,3 @@ +.commits-compare-form button { + float: none !important; +} diff --git a/assets/tex/template.tex b/assets/tex/template.tex index 1cf94507cf..8c98f8d726 100644 --- a/assets/tex/template.tex +++ b/assets/tex/template.tex @@ -152,7 +152,7 @@ \begin{document} $if(title)$ -\ULCornerWallPaper{1}{../../assets/tex/coverpage.pdf} +\ULCornerWallPaper{1}{../../../assets/tex/coverpage.pdf} \maketitle \ClearWallPaper $endif$ diff --git a/doc/source/back-end-code/article.rst b/doc/source/back-end-code/article.rst index acd20ce348..f9ab8e8f3a 100644 --- a/doc/source/back-end-code/article.rst +++ b/doc/source/back-end-code/article.rst @@ -16,4 +16,4 @@ Vues (``views.py``) =================== .. automodule:: zds.article.views - :members: \ No newline at end of file + :members: diff --git a/doc/source/back-end-code/search.rst b/doc/source/back-end-code/search.rst new file mode 100644 index 0000000000..a697cd0663 --- /dev/null +++ b/doc/source/back-end-code/search.rst @@ -0,0 +1,25 @@ +========================== +La recherche (``search/``) +========================== + +Module situé dans ``zds/search/``. + +.. contents:: Fichiers documentés : + +Modèles (``models.py``) +======================= + +.. automodule:: zds.search.models + :members: + +Vues (``views.py``) +=================== + +.. automodule:: zds.search.views + :members: + +Les utilitaires (``utils.py``) +============================== + +.. automodule:: zds.search.utils + :members: diff --git a/doc/source/back-end-code/tutorialv2.rst b/doc/source/back-end-code/tutorialv2.rst new file mode 100644 index 0000000000..9595c8bb15 --- /dev/null +++ b/doc/source/back-end-code/tutorialv2.rst @@ -0,0 +1,64 @@ +========================================== +Les tutoriels v2 (ZEP12) (``tutorialv2/``) +========================================== + +Module situé dans ``zds/tutorialv2/``. + +.. contents:: Fichiers documentés : + +Modèles (``models/``) +===================== + +Modèles de la base de donnée (``models_database.py``) +----------------------------------------------------- + +.. automodule:: zds.tutorialv2.models.models_database + :members: + + +Modèles "versionnés" (``models_versioned.py``) +---------------------------------------------- + +.. automodule:: zds.tutorialv2.models.models_versioned + :members: + +Vues (``views/``) +================= + +Contenus (``views/views_contents.py``) +-------------------------------------- + +.. automodule:: zds.tutorialv2.views.views_contents + :members: + + +Validations (``views/views_validations.py``) +-------------------------------------------- + +.. automodule:: zds.tutorialv2.views.views_validations + :members: + +Contenus publiés (``views/views_published.py``) +----------------------------------------------- + +.. automodule:: zds.tutorialv2.views.views_published + :members: + +Mixins (``mixins.py``) +====================== + +.. automodule:: zds.tutorialv2.mixins + :members: + +Les forumulaires (``forms.py``) +=============================== + +.. automodule:: zds.tutorialv2.forms + :members: + +Les utilitaires (``utils.py``) +============================== + +.. automodule:: zds.tutorialv2.utils + :members: + diff --git a/doc/source/back-end/article.rst b/doc/source/back-end/article.rst deleted file mode 100644 index 1ad0f7dff2..0000000000 --- a/doc/source/back-end/article.rst +++ /dev/null @@ -1,8 +0,0 @@ -============ -Les articles -============ - -A venir -======= - - diff --git a/doc/source/back-end/contents.rst b/doc/source/back-end/contents.rst new file mode 100644 index 0000000000..c07305adc3 --- /dev/null +++ b/doc/source/back-end/contents.rst @@ -0,0 +1,550 @@ +======================================= +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 `__) + +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) `__. 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``. diff --git a/doc/source/back-end/contents_manifest.rst b/doc/source/back-end/contents_manifest.rst new file mode 100644 index 0000000000..76b34fa981 --- /dev/null +++ b/doc/source/back-end/contents_manifest.rst @@ -0,0 +1,161 @@ +========================= +Les fichiers de manifeste +========================= + +Chaque contenu publiable (tutoriel et article) est décrit par un fichier de manifeste écrit au format JSON. + +Ce fichier de manifeste a pour but d'exprimer, versionner et instancier les informations et méta-informations du contenu tout au long du workflow de publication. + +Les informations en question sont l'architecture, les titres, les liens vers les sources, les informations de license ainsi que la version du fichier de manifeste lui-même. + +Le fichier de manifeste est intrinsèquement lié à un interpréteur qui est inclus dans le module de contenu associé. + +Les versions du manifeste +========================= + +Nomenclature +------------ + +La version du manifeste (et de son interpréteur) suit une nomenclature du type "semantic version" (SemVer), c'est-à-dire que la version est séparée en trois parties selon le format v X.Y.Z + +- X : numéro de version majeur +- Y : numéro de version à ajout fonctionnel mineur +- Z : numéro de version de correction de bug + +Plus précisément : + +- L'incrémentation de X signifie que la nouvelle version est potentiellement incompatible avec la version X-1. Un outil de migration doit alors être créé. +- L'incrémentation de Y signifie que la nouvelle version possède une compatibilité descendante avec la version X.Y-1 mais que la compatibilité ascendante n'est pas assurée. C'est-à-dire que le nouvel interpréteur peut interpréter un manifeste de type X.Y-1 + mais l'ancien interpréteur ne peut pas interpréter un manifeste X.Y. Le cas typique d'incrémentation de Y est le passage d'obligatoire à optionnel d'un champ du manifeste. +- L'incrémentation de Z assure la compatibilité ascendante ET descendante, les cas typiques d'incrémentation de Z est l'ajout d'un champ optionnel au manifeste. + +Sauf cas exceptionnel, la numérotation de X commence à 1, la numérotation de Y commence à 0, la numérotation de Z commence à 0. + +La version du manifeste est donnée par le champ éponyme situé à la racine du manifeste ( ```{ version="2.0.0"}```). +L'absence du champ version est interprétée comme ``̀{version="1.0.0"}```. +Les 0 non significatifs sont optionnels ainsi ```{version="1"}``` est strictement équivalent à ```{version:"1.0"}``` lui-même strictement équivalent à ```{version:"1.0.0"}```. + +Version 1.0 +----------- + +La version 1.0 définit trois types de manifeste selon que nous faisons face à un article, un mini tutoriel ou un big tutoriel. + +MINI TUTO ++++++++++ + +.. sourcecode:: json + + { + "title": "Mon Tutoriel No10", + "description": "Description du Tutoriel No10", + "type": "MINI", + "introduction": "introduction.md", + "conclusion": "conclusion.md" + } + +BIG TUTO +++++++++ + +.. sourcecode:: json + + { + "title": "3D temps réel avec Irrlicht", + "description": "3D temps réel avec Irrlicht", + "type": "BIG", + "licence": "Tous droits réservés", + "introduction": "introduction.md", + "conclusion": "conclusion.md", + "parts": [ + { + "pk": 7, + "title": "Chapitres de base", + "introduction": "7_chapitres-de-base/introduction.md", + "conclusion": "7_chapitres-de-base/conclusion.md", + "chapters": [ + { + "pk": 25, + "title": "Introduction", + "introduction": "7_chapitres-de-base/25_introduction/introduction.md", + "conclusion": "7_chapitres-de-base/25_introduction/conclusion.md", + "extracts": [ + { + "pk": 87, + "title": "Ce qu'est un moteur 3D", + "text": "7_chapitres-de-base/25_introduction/87_ce-quest-un-moteur-3d.md" + }, + { + "pk": 88, + "title": "Irrlicht", + "text": "7_chapitres-de-base/25_introduction/88_irrlicht.md" + } + ] + },(...) + ] + }, (...) + ] + } + +Article ++++++++ + +.. sourcecode:: json + + { + "title": "Mon Article No5", + "description": "Description de l'article No5", + "type": "article", + "text": "text.md" + } + + +Version 2.0 +----------- + +.. sourcecode:: json + + { + "object": "container", + "slug": "un-tutoriel", + "title": "Un tutoriel", + "introduction": "introduction.md", + "conclusion": "conclusion.md", + "version": 2, + "description": "Une description", + "type": "TUTORIAL", + "licence": "Beerware", + "children": [ + { + "object": "container", + "slug": "titre-de-mon-chapitre", + "title": "Titre de mon chapitre", + "introduction": "titre-de-mon-chapitre/introduction.md", + "conclusion": "titre-de-mon-chapitre/conclusion.md", + "children": [ + { + "object": "extract", + "slug": "titre-de-mon-extrait", + "title": "Titre de mon extrait", + "text": "titre-de-mon-chapitre/titre-de-mon-extrait.md" + }, + (...) + ] + }, + (...) + ] + } + +1. ``type`` : Le type de contenu, vaut "TUTORIAL" ou "ARTICLE". **Obligatoire** +2. ``description`` : La description du contenu. Est affichée comme sous-titre dans la page finale. **Obligatoire** +3. ``title`` : Le titre du contenu. **Obligatoire** +4. ``slug`` : slug du contenu qui permet de faire une url SEO-friendly. **Obligatoire**. ATENTION : si ce slug existe déjà dans notre base de données, il est possible qu'un nombre lui soit ajouté +5. ``introduction`` : le nom du fichier Mardown qui possède l'introduction. Il doit pointer vers le dossier courant. *Optionnel mais conseillé* +6. ``conclusion`` : le nom du fichier Mardown qui possède la conclusion. Il doit pointer vers le dossier courant. *Optionnel mais conseillé* +7. ``licence`` : nom complet de la license. *A priori* les licences "CC" et "Tous drois réservés" sont supportées. Le support de toute autre licence dépendra du site utilisant le code de ZdS (fork) que vous visez. **Obligatoire** +8. ``children`` : tableau contenant l'architecture du contenu. **Obligatoire** + 1. ``object`` : type d'enfant (*container* ou *extract*, selon qu'il s'agisse d'une section ou d'un texte). **Obligatoire** + 2. ``title`` : le titre de l'enfant. **Obligatoire** + 3. ``slug`` : le slug de l'enfant pour créer une url SEO-friendly, doit être unique dans le contenu, le slug est utilisé pour trouver le chemin vers l'enfant dans le système de fichier si c'est une section. **obligatoire** + 4. ``introduction`` : nom du fichier contenant l'introduction quand l'enfant est de type *container*. *Optionnel mais conseillé* + 5. ``conclusion`` : nom du fichier contenant la conclusion quand l'enfant est de type *container*. *Optionnel mais conseillé* + 6. ``children`` : tableau vers les enfants de niveau inférieur si l'enfant est de type *container*. **Obligatoire** + 7. ``text`` : nom du fichier contenant le texte quand l'enfant est de type *extract*. Nous conseillons de garder la convention ``nom de fichier = slug.md`` mais rien n'est obligatoire à ce sujet. **Obligatoire** diff --git a/doc/source/back-end/search.rst b/doc/source/back-end/search.rst index 08d9deeca6..c9102c1bf3 100644 --- a/doc/source/back-end/search.rst +++ b/doc/source/back-end/search.rst @@ -14,13 +14,13 @@ L'indexation est faite de telle façon qu'on puisse rechercher sur les élément - Les tutoriels (les parties, les chapitres, les extraits) - Les articles (titre, description, date de publication, contenu en markdown) - - Les sujets (titre, sous titres, auteurs, le contenu en markdown, le nom du forum) + - Les sujets (titre, sous-titre, auteurs, le contenu en markdown, le nom du forum) - Les réponses au sujets (auteur et contenu en markdown) L'indexation est réalisée à la demande, même si un jour, elle pourrait être en `temps réel `_. Aujourd'hui, l'indexation est réalisée toutes les trentaines de minutes, sur le serveur de production. Tout le contenu est ré-indexé à chaque fois, ce qui prend 100 pourcents du processus toutes les 5 minutes. -Des solutions sont présentes dans `la documentation `_, sans qu'on puisse réellement `les appliquer `_. +Des solutions sont présentes dans `la documentation d'Haystack (en) `_, sans qu'on puisse réellement `les appliquer `_. La recherche ============ @@ -36,24 +36,24 @@ Les critères de recherches sont uniquement sur le type de contenu (tutoriel, ar Dans le code ? ============== -On utilise pour **l'indexation** une **librairie** en python **Haystack** et le **moteur de recherche Solr**. +On utilise pour **l'indexation** une **bibliothèque** Python nommée **Haystack** et le **moteur de recherche Solr**. .. figure:: ../images/search/schema-recherche-lib.png :align: center -Pour l'indexation, on indique à Haystack quels contenus on doit indexer. Haystack appelle une librairie Python nommée PySolr. -PySolr n'est jamais utilisé directement dans le code, on doit toujours passer par la librairie Haystack. PySolr effectue des appels à une API REST exposée par le moteur de recherche Solr. +Pour l'indexation, on indique à Haystack quels contenus on doit indexer. Haystack appelle une bibliothèque Python nommée PySolr. +PySolr n'est jamais utilisé directement dans le code, on doit toujours passer par la bibliothèque Haystack. PySolr effectue des appels à une API REST exposée par le moteur de recherche Solr. -Pour la recherche, c'est le même principe, librairie Haystack -> librairie PySolr -> l'API REST de Solr. +Pour la recherche, c'est le même principe : bibliothèque Haystack -> bibliothèque PySolr -> l'API REST de Solr. -Pourquoi avoir utilisé Solr, ne peut t-on pas appeller Solr directement ? -------------------------------------------------------------------------- +Pourquoi avoir utilisé Haystack, ne peut t-on pas appeller Solr directement ? +----------------------------------------------------------------------------- On pourrait directement appeler Solr mais Haystack nous propose plusieurs avantages : - - Nous permet d'être indépendant, du moteur de recherche utilisé, aujourd'hui, on utilise Solr mais demain, on pourrait assez facilement le remplacer par un autre moteur de recherche tel que `Xapian `_, `Elasticsearch `_ ou `Whoosh `_ + - Nous permet d'être indépendant, du moteur de recherche utilisé, aujourd'hui, on utilise Solr mais demain, on pourrait assez facilement le remplacer par un autre moteur de recherche tel que `Xapian (en) `_, `Elasticsearch (en) `_ ou `Whoosh (en) `_ - Assez facile à utiliser - - Nous permet d'avoir relativement facilement, les facets, la recherche spatiale, suggestions des mots clés, autocompletion, … . - - Nous permet de généré les fichiers de configurations plus facilement. + - Nous permet d'avoir relativement facilement, les facets, la recherche spatiale, suggestions des mots clés, autocomplétion… + - Nous permet de générer les fichiers de configuration plus facilement. - Libre (BSD) et gratuit. Pourquoi Solr et pas un autre moteur de recherche ? @@ -70,7 +70,7 @@ Comment fonctionne le code de l'indexation ? Tout d'abord, avant d'attaquer le code, il faut bien faire la différence entre : - - **Le contenu "indexés"**, c'est-à-dire le contenu sur lesquel **Solr va chercher** quand un utilisateur utilise la recherche. + - **Le contenu "indexé"**, c'est-à-dire le contenu sur lequel **Solr va chercher** quand un utilisateur utilise la recherche. - **Le contenu "stocké"**, c'est le contenu qui va **être retourné après la recherche**. C'est très utile, si vous voulez afficher des informations supplémentaires lors de l'affichage des résultats. Par exemple, quand on indexe, un tutoriel, on peut souhaiter lors de l'affichage, afficher tous les noms des auteurs sans pour autant permettre à l'utilisateur de rechercher sur le nom des auteurs. C'est à ça que servent les informations stockés. @@ -99,9 +99,9 @@ Prenons un exemple, cette classe permet d'indexer les sujets du forum. def index_queryset(self, using=None): return self.get_model().objects.filter(is_visible=True) -Le premier champ, permet de définir un fichier de *templates*, ou seront stockés les informations à **indexer** et les **informations stockés**. +Le premier champ, permet de définir un fichier de *template*, ou seront gardées les informations à **indexer** et les **informations stockés**. -Grâce au nom du module, et du champ, on peut déterminer où ce trouve le fichier de *templates*. Le *template*, se trouve, par défaut dans le dossier ``templates/search/indexes/nom_module/nom_classe_nom_champs.txt``. +Grâce au nom du module, et du champ, on peut déterminer où ce trouve le fichier de *template*. Le *template*, se trouve, par défaut dans le dossier ``templates/search/indexes/nom_module/nom_classe_nom_champs.txt``. Par exemple, le fichier *template* qui définit le contenu à indexer et les informations stockés pour les sujets sera dans ``templates/search/indexes/forum/post_text.txt``. Voici le fichier de *template*, on voit ici, qu'on indexe deux types d'informations : le nom de l'auteur et le contenu en markdown, retourné par la fonction ``Text``. @@ -111,7 +111,7 @@ Voici le fichier de *template*, on voit ici, qu'on indexe deux types d'informati {{ object.author.username }} {{ object.text }} -Les autres champs de la classe, forment les critères de recherches. C'est les champs sur lesquels, l'utilisateur pourrait rechercher si il le souhaite. +Les autres champs de la classe, forment les critères de recherches. C'est les champs sur lesquels, l'utilisateur pourrait rechercher s'il le souhaite. L'utilisateur pourrait donc rechercher, si on lui fournit l'interface graphique, sur le titre, le sous-titre, l'auteur ou la date de publication. .. attention:: @@ -132,7 +132,29 @@ Le dernier champ est précisé comme "stocké" mais pas indexé, c'est-à-dire q La première méthode permet de définir le modèle du contenu à indexer et la deuxième méthode, permet d'exclure du contenu qu'on ne voudrait pas indexer. Plus d'information : - - `Documentation de Haystack `_ + - `Documentation de Haystack (en) `_ + +Le cas particulier de l'indexation des tutoriels et articles +------------------------------------------------------------ + +Depuis la ZEP-12, les tables dans la base de données pour les parties, chapitres et extraits ont été supprimées. + +Les contenus (tutoriels et articles) sont stockés dans des tables spéciales qui ne servent qu'à l'indexation. +Ces tables sont nommés ``SearchIndexContent``, ``SearchIndexContainer``, ``SearchIndexExtract``, ``SearchIndexAuthors`` et ``SearchIndexTag``. + +Mais ces tables doivent-être remplies, il est impossible de le faire à la publication de façon synchrone et bloquante, car cette opération prend du temps et des I/O. Pour rappel, + +- I/O : ``2 + 2 * nombre de conteneurs + nombre d'extraits`` (bien que cette opération se fasse au travers d'une archive compressée ZIP, ce qui modifie les performances) +- Pour la base de données : + - En suppression : ``1 + nombre de conteneurs + nombre d'extraits``, + - En ajout : ``1 + nombre de conteneurs + nombre d'extraits`` + +Pour rappel, lors de la publication d'un contenu, dans la table ``PublishableContent``, le champ ``must_reindex`` est passé à ``True`` indiquant que le contenu doit-être ré-indexé. + +Plus tard, souvent avant l'utilisation de la commande ``python manage.py rebuild_index``, la commande ``python manage.py index_content`` est utilisée. +Elle permet de copier les données des articles et tutoriels dans les tables de recherche. Elle utilise pour cela la version du contenu qui est stocké dans le fichier ZIP généré à la publication, afin de gagner du temps. + +Vous pouvez trouver plus d'information, sur cette commande `ici <#utilisation-de-la-commande-index-content>`_. Comment lancer l'indexation et/ou comment vérifier les données indexées ? ------------------------------------------------------------------------- @@ -141,7 +163,7 @@ Il faut installer et démarrer Solr, régénérer le ``schema.xml`` et réindexe Cette procédure est nécessaire à chaque modification des critères d'indexation. -Si vous voulez, vérifier les données indexées, il faut vous rendre dans l'interface d'administration de Solr. Entrez dans un navigateur, l'adresse `http://localhost:8983/solr/ `_ pour vous rendre dans l'interface d'administration +Si vous voulez, vérifier les données indexées, il faut vous rendre dans l'interface d'administration de Solr. Entrez dans un navigateur, l'adresse `http://localhost:8983/solr/ `_ pour vous rendre dans l'interface d'administration. Sélectionnez dans la colonne à gauche, à l'aide du menu déroulant le nom de votre collection. @@ -158,12 +180,12 @@ Deux options s'offrent à vous : .. figure:: ../images/search/interface-query.png :align: center - De nombreuses abbréviations sont utilisées dans cette interface, vous pouvez rechercher leurs significations dans la `Documentation de Solr `_. + De nombreuses abbréviations sont utilisées dans cette interface, vous pouvez rechercher leurs significations dans la `documentation de Solr (en) `_. - Un seul champ va nous intéresser, il est nommé "q". Ce champs vous permet de définir les mots clés recherchés. Dans l'exemple, du dessus, j'ai choisi le mot clé Java. - Cliquez sur "Execute Query", le bouton bleu en bas de l'interface, pour effectuer la recherche. Vous avez ainsi les résultats qui s'affichent dans la partie de gauche + Un seul champ va nous intéresser, il est nommé "q". Ce champs vous permet de définir les mots-clés recherchés. Dans l'exemple, du dessus, j'ai choisi le mot clé "Java". + Cliquez sur "Execute Query", le bouton bleu en bas de l'interface, pour effectuer la recherche. Vous avez ainsi les résultats qui s'affichent dans la partie de gauche. - - Vous pouvez aussi avoir besoin de vérifier si tel champ indexe des données, ou quels sont ces données. Pour cela, il faut vous rendre dans l'interface "Schema browser". + - Vous pouvez aussi avoir besoin de vérifier si tel champ indexe des données, ou quelles sont ces données. Pour cela, il faut vous rendre dans l'interface "Schema browser". Pour cela, utilisez le menu de gauche. .. figure:: ../images/search/schemabrowser.png @@ -174,7 +196,7 @@ Vous arrivez sur une nouvelle interface : .. figure:: ../images/search/interface-webbrowser.png :align: center -En haut, à gauche, vous devez définir le nom du champ, sélectionner en un grâce à la liste déroutante. Dans la capture, j'ai choisi le champ subtitle. +En haut, à gauche, vous devez définir le nom du champ, sélectionnez-en un grâce à la liste déroutante. Dans la capture, j'ai choisi le champ "subtitle". Vous pouvez lire très facilement si le champ est indexé ou/et stocké grâce au tableau sur la droite. @@ -210,9 +232,9 @@ Quels sont les fichiers de configuration importants ? ===================================================== Les fichiers de configurations les plus importants sont le fichier ``schema.xml`` et le fichier ``solrconfig.xml``. -Ces deux fichiers sont stockés dans le dossier d'installation de Solr et dans les sous-dossiers suivants : ``/example/solr/collection1/conf/schema.xml``. +Ces deux fichiers sont stockés dans le dossier d'installation de Solr à l'intérieur du sous-dossier ``example/solr/collection1/conf/``. -Le fichier schema.xml permet de définir les types de champs qu'on pourrait créer, par exemple, définir ce qu'est un champ de type texte, ce qu'en est un de type date… +Le fichier ``schema.xml`` permet de définir les types de champs qu'on pourrait créer, par exemple, définir ce qu'est un champ de type texte, ce qu'en est un de type date… Il permet de définir aussi des filtres (et des *tokenizers* (des filtres qui découpent des mots)) lors de l'indexation du contenu et avant la recherche. Le fichier de ``solrconfig.xml``, permet de définir les paramètres de configuration du moteur de recherche. On a gardé les paramètres par défaut. @@ -221,13 +243,13 @@ La question qui se pose naturellement est pourquoi veut-on appliquer des filtres Tout simplement car il faut traiter le contenu avant de l'indexer car certains mots ne doivent pas apparaître dans l'indexation. Par exemple, les mots comme "le", "la", "les", "ou", "de", "par" ne sont pas des mots importants et ne vont pas permettre de représenter ce que l'utilisateur cherche. -Il est aussi très important d'enlever les radicaux et les pluriels des mots car ils ne sont pas nécessaires. +Il est aussi très important d'enlever les radicaux et les pluriels des mots car ils ne sont pas nécessaires. Si un utilisateur veut rechercher, par exemple, la phrase "Les Cornichons n'aiment pas les poissons", lors de l'indexation et de la recherche, on va appliquer des filtres pour découper les mots. On aura ainsi dans le contenu "Les" "Conichons", "n'aiment", "pas", "les", "poissons". Ensuite le moteur de recherche, peut choisir d'enlever les pluriels, on aura donc "Le" "Conichon", "aime", "pas", "le", "poisson". On peut aussi choisir de supprimer tous les mots pas ou peu importants, on aura donc à la fin de cette étape : "Conichon", "aime", "pas", "poisson". Ces quatres mots formeront le contenu à indexer. Le ``schema.xml`` comme dit plus haut permet de définir ces filtres, lors de la génération du fichier ``schema.xml`` par Haystack (la commande ``python manage.py build_solr_schema``), les filtres sont ajoutés. -Le projet Zeste de Savoir a eu besoin de définir des filtres. En effet, les filtres par défaut traitent uniquement du contenu en anglais. Quand la librairie Haystack va générer le fichier ``schema.xml``, le projet va remplacer le *template* de génération par celui du projet qui inclut les filtres. -Ce fichier de *templates* est défini dans ``templates/search_configuration/solr.xml``. Les filtres appliqués sont dans la balise ``fieldType`` avec le nom ``text_french``. +Le projet Zeste de Savoir a eu besoin de définir des filtres. En effet, les filtres par défaut traitent uniquement du contenu en anglais. Quand la bibliothèque Haystack va générer le fichier ``schema.xml``, le projet va remplacer le *template* de génération par celui du projet qui inclut les filtres. +Ce fichier de *template* est défini dans ``templates/search_configuration/solr.xml``. Les filtres appliqués sont dans la balise ``fieldType`` avec le nom ``text_french``. Vous pouvez constater les filtres avec l'interface d'administration web de Solr. @@ -238,4 +260,21 @@ Allez dans l'administration `http://localhost:8983/solr/ chapitre - - |-> introduction - |-> - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> - |-> conclusion - - -Dans ce cas, le nombre de chapitre est bien limité à 1 et la présentation d'un minituto consiste à -présenter un seul chapitre de la structure globale. - -Les bigtutos ------------- - -Un bigtuto, si on repense à nos niveaux de structure, est un tutoriel dans lequel on peut avoir plusieurs -parties, chaque partie pouvant contenir plusieurs chapitres et chaque chapitre pouvant à leur tour -contenir plusieurs extraits. -Les bigtutos reprennent donc ici tous les éléments de la structure. Ce format est adapté aux tutoriels -dont le contenu est assez conséquent, et demandent beaucoup de structuration. On pourrait le représenter ainsi : - -.. sourcecode:: none - - Tutoriels - - -> partie 1 - - |-> chapitre 1 - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - |-> chapitre 2 - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - |-> chapitre n - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - -> partie 2 - - |-> chapitre 1 - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - |-> chapitre 2 - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - |-> chapitre n - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - -> partie n - - |-> chapitre 1 - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - |-> chapitre 2 - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - |-> chapitre n - - |-> introduction - |-> extrait 1 - |-> extrait 2 - |-> extrait ... - |-> extrait n - |-> conclusion - - -Import de tutoriels -=================== - -Zeste de Savoir permet d'importer des tutoriels provenant de sources extérieures. Deux formats d'import sont actuellement supportés. - -Les archives zip ----------------- - -Si vous avez commencé a rédiger un tutoriel via l'éditeur en ligne de Zeste de Savoir, vous avez téléchargé l'archive correspondante et vous avez fait des modifications sur les fichiers en hors-ligne, et vous souhaitez maintenant importer ces modifications sur le site. Il suffit de faire une archive zip du répertoire dans lequel se trouvent les fichiers de votre tutoriel et de renseigner les deux champs relatifs à l'import d'une archive, puis de cliquer sur importer. - -.. figure:: ../images/tutorial/import-archive.png - :align: center - -.. attention:: - - Le rajout d'une partie, d'un chapitre ou d'un tutoriel n'est pas encore supporté dans l'import. Le module n'importera que ce qui a été **modifié** dans les fichiers markdown. - -Le format .tuto ---------------- - -Il s'agit du format dans lequel étaient exportés les tutoriels sur le Site du Zéro. C'est un format de type xml. Cependant, pour qu'il soit -importable sur ZdS il faut le transformer à l'aide d'un outil de conversion en semi-markdown. Si vous avez besoin d'importer un tutoriel -de ce format, vous devez contacter le staff de Zeste de Savoir pour que votre fichier ``.tuto`` soit converti en semi markdown. - -Vous aurez aussi besoin du pack d'images (au format zip) qui sont utilisés dans votre tutoriel. - -Il ne vous restera plus qu'à renseigner les champs relatifs à l'import de ``.tuto`` pour importer le vôtre. - -.. figure:: ../images/tutorial/import-tuto.png - :align: center - -.. attention:: - - L'import du tutoriel peut prendre beaucoup de temps en fonction de la taille de votre tutoriel. - -Cycle de vie des tutoriels -========================== - -Quelque soit le type de tutoriel, le cycle de vie de celui-ci reste toujours le même. -Un tutoriel peut être rédigé par un ou plusieurs auteurs. Chaque modification sur le tutoriel -est conservée afin de pouvoir retrouver l'historique des modifications et éventuellement -récupérer le contenu perdu. Lorsqu'un tutoriel est créé il rentre dans sa première étape. - -Le brouillon ------------- - -Le brouillon est la première étape du cycle de vie d'un tutoriel. Il donne toujours l'état -le plus récent du contenu d'un tutoriel vu par les auteurs. Chaque fois que le contenu du -tutoriel 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 est arrivé à un certain niveau de maturité, et qu'ils souhaitent -recueillir les premiers retours de lecteurs, ils se doivent de mettre à disposition de la communauté le tutoriel en -lecture seule. C'est le mode bêta. - -La procédure voudrait que lors de l'ouverture d'une bêta, l'auteur crée un sujet dans le forum des tutoriels -en bêta, en postant le lien vers la version bêta du tutoriel. - -.. attention:: - - Le lien de la bêta, peut être trouvé via votre profil utilisateur, et est sous la forme ``/tutoriels/beta//``. Le lien est aussi disponible via ``/tutoriel/off///?version=sha``. Seule la première forme doit etre donnée au public. - -En fait lorsqu'un tutoriel est en mode bêta, il s'agit d'une version précise qui est mise -dans ce mode. On peut continuer à mettre à jour la version brouillon pour rajouter de nouveaux chapitres -à notre tutoriel, pendant ce temps, la communauté lit une version figée de notre tutoriel. L'avantage étant que -si le tutoriel prend beaucoup de temps à lire, le lecteur n'a pas de mauvaise surprise de mise à jour -pendant sa lecture. Les auteurs quant à eux doivent mettre à jour manuellement leur version bêta et ainsi -ils contrôlent pleinement ce qu'ils mettent à disposition des lecteurs. - -La validation -------------- - -Une fois que l'auteur a eu assez de retour sur son tutoriel, et qu'il estime qu'il est prêt à être publié, -il décide d'envoyer son tutoriel en validation. - -L'envoi en validation n'est pas définitif, dans le sens où, vous pouvez à tout moment mettre à jour la version -qui se trouve du coté des validateurs. É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. Ce qui pourrait non seulement -ralentir le processus de validation de votre tutoriel, mais décourager aussi le validateur. Donc un conseil à donner serait -de n'envoyer que du contenu sûr en validation. - -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 son côté. - -La publication --------------- - -Une fois le contenu, lu et relu par l'équipe staff, le tutoriel est publié. Il faut bien préciser que le processus -de validation peut être assez long en fonction de la taille du tutoriel traité. Un tutoriel n'est pas obligé -d'être publié à la suite d'une demande de validation, il peut aussi être rejeté. Dans tous les cas, un historique -de validation est disponible pour les membres du staff. - -La publication d'un tutoriel entraîne la création d'export en plusieurs formats. On a les formats - -- Markdown : disponible uniquement pour les membres du staff et les auteurs des tutoriels -- HTML -- PDF -- EPUB : format de lecture adapté aux liseuses -- Archive : un export de l'archive contenant le dépôt git du projet. - -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 (blocs custom), qui devront être réglés plus tard avec la `ZEP 05 `_) - -L'entraide ----------- - -Afin d'aider les auteurs de tutoriels à 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 compétences 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 tutoriel et recherche d'un repreneur - -L'ensemble des tutoriels à la recherche d'aide est visible via la page "/tutoriels/aides/". Cette page génère un tableau récapitulatif de toutes les demandes d'aides pour les différents tutoriels et des filtres peuvent être appliqués. - -Il est également possible **pour tout membre qui n'est pas auteur du tutoriel consulté** de signaler une erreur, en employant le bouton prévu à cet effet et situé en bas d'une page du tutoriel (il est également présent en bas d'un chapitre, s'il s'agit d'un big-tutoriel). - - - .. 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 tutoriel. Cliquer sur celui-ci ouvre une boite de dialogue : - - .. figure:: ../images/tutorial/warn-typo-dial.png - :align: center - - Boite de dialogue permetant 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 a donner des détails. Cliquer sur "Envoyer" envera un message privé aux auteurs du tutoriels, reprenant votre message. Vous participerez également à la conversation, afin que les auteurs puissent vous demander plus de détails le cas échéant. - -L'aspect technique -================== - -Le stockage dans la base de données ------------------------------------ - -Aujourd'hui la base de données est utilisée comme zone tampon, surtout parce que Django propose déjà des méthodes -d'enregistrement des objets en base de données de manière concurrentes et *thread safe*. L'idée étant de s'en -détacher à terme. -La version stockée dans la base de données est le dernier état, c'est-à-dire l'état de la version en -brouillon. Il ne faut donc pas aller chercher en base de données les informations pour les afficher. - -Chaque tutoriel possède trois attributs principaux : - -- sha_draft : le hash du commit de la version brouillon -- sha_beta : le hash du commit de la version bêta -- sha_validation : le hash du commit de la version validation -- sha_public : le hash du commit de la version publique - -On peut les voir comme des pointeurs sur chaque version, et le fait qu'ils soient stockés en base les rends -plus accessibles. À terme aussi, on devrait pouvoir en faire des branches. - -Il faut aussi noter qu'on ne stocke pas le contenu (introduction, conclusion, extraits) directement en base de données, on stocke uniquement les chemins relatifs vers les fichiers markdown qui contiennent le contenu. - -Les données versionnées ------------------------ - -Le module des tutoriels se base sur **git** pour versionner son contenu. Physiquement, nous avons un répertoire pour chaque tutoriel (point d'initialisation du dépôt). À l'intérieur nous avons un répertoire par partie, et dans chaque partie, un répertoire par chapitre, et pour chaque chapitre, un fichier par extrait. - -Pour éviter les conflits dans les noms de fichier, le chemin vers un extrait aura souvent le modèle suivant : - -``[id_partie]_[slug_partie]/[id_chap]_[slug_chap]/[id_extrait]_[slug_extrait].md`` - -Pour pouvoir versionner tout ceci, nous avons un fichier nommé ``masnifest.json`` chargé de stocker l'ensemble des métadonnées versionnées du tutoriel. Ce fichier manifest est lui aussi versionné. Pour chaque version, il suffit donc de lire ce fichier pour reconstituer un tutoriel. C'est un fichier json qui reprend la structure du document, et les différents chemins relatifs vers le contenu. Les métadonnées stockées sont : - -- Le titre du tutoriel, des parties, des chapitres et des extraits -- Le sous-titre du tutoriel -- La licence du tutoriel -- Les divers chemins relatifs vers les fichiers markdown - -L'objectif étant d'arriver à tout versionner (catégories, ...) et de ne plus avoir à lire dans la base de données pour afficher quelque chose. - -**NB** : A chaque modification d'un élément du tutoriel, l'auteur doit renseigner un message de suivi (ou message de révision). Ce message (qui se veut court) permet de résumer les modifications qui ont été faites lors de l'édition. - -Qu'en est-il des images ? -+++++++++++++++++++++++++ - -Le versionning des images d'un tutoriel (celles qui font partie de la galerie du tuto) continue à faire débat, et il a été décidé pour le moment de ne pas les versionner dans un premier temps, 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 trouver 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 faudra mieux y réfléchir pour une version 2 afin de proposer la rédaction totalement en mode hors ligne. - -Quid des tutoriels publiés ? -++++++++++++++++++++++++++++ - -Les tutoriels en *offline* sont tous versionnés, et sont dans le répertoire ``tutoriels_private``. Lorsqu'ils sont validés le traitement suivant est appliqué. - -- On copie le dépôt du tutoriel dans le répertoire ``tutoriels_public`` -- On va chercher dans l'historique du dépôt les fichiers correspondant à la version publique -- On converti ces fichiers en html (en utilisant zMarkdown) -- On stocke les fichiers html sur le serveur. - -Ainsi, pour lire un tutoriel public, on a juste besoin de lire les fichiers html déjà convertis. - -Et si un auteur a besoin d'aide ? -+++++++++++++++++++++++++++++++++ - -Afin d'aider les auteurs de tutoriels à 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. La liste des compétences `est reprise ci-dessus <#l-entraide>`_. - -L'ensemble des tutoriels à la recherche d'aide est visible via la page "help.html" (template dans le fichier ``templates/tutorial/tutorial/help.html``). Cette page génère un tableau récapitulatif de toutes les demandes d'aides pour les différents tutoriels et des filtres peuvent être appliqués. Toutes les données servant à peupler ce tableau sont renvoyées via la méthode ``help_tutorial()`` dans le fichier ``zds/tutorial/views.py``. Cette méthode peut prendre en compte un argument en GET nommé type désignant le filtre à appliquer. Cet argument représente le slug d'une des options de la liste précédentes. -En cas d'absence du paramètre, tout les tutoriels ayant au moins une demande d'aide d'activées ou en bêta sont renvoyé au template. -De nouveaux types de demande d'aide peuvent-être rajouté via l'interface d'administration Django dans la classe ``Utils.HelpWriting``. - -Sur la page d'entraide, les tutoriels sont ordonnées de la manière suivante (par ordre d'impact/priorité) : - -- Les tutos déjà publiés passent à la fin de la liste ; -- Puis on trie par le nombre d'aides différentes demandées ; -- Puis on trie par la date de mise à jour. - -Quelques données de test sont présentes dans le fichier ``fixtures/aide_tuto_media.yaml``. En chargeant ces dernières, un tuto peut alors être modifié pour recevoir des demandes d'aides (en allant les sélectionner dans la liste à cet effet lors de l'édition du tuto). -Pour chaque données de test, il faut aussi passer par l'interface d'administration Django pour ajouter les images relatives à ces aides (limites techniques du chargement automatique). Quatres illustrations sont présentes dans le dossier de fixtures correspondant aux quatres aides présentes dans les fixtures. - -Pour charger ces fixtures, il ne faut pas utiliser la routine habituelle ``manage.py loaddata``. En effet, les demandes d'aide ont besoin d'être liées à des images. -C'est pourquoi, nous utilisons la factory ``zds.utils.factories.HelpWritingFactory`` pour mettre en place ces fixtures. -Le code sera donc - -.. sourcecode:: bash - - python manage.py load_factory_data fixtures/advanced/aide_tuto_media.yaml \ No newline at end of file diff --git a/doc/source/front-end/template-tags.rst b/doc/source/front-end/template-tags.rst index c255bb3e92..1a6c4fffa9 100644 --- a/doc/source/front-end/template-tags.rst +++ b/doc/source/front-end/template-tags.rst @@ -1,25 +1,18 @@ -================================== -Elements de gabarits personnalisés -================================== +=================================== +Elements de templates personnalisés +=================================== Le dossier ``zds/utils/templatetags/`` contient un ensemble de tags et filtres personnalisés pouvant être utilisés dans les gabarits (*templates*), `voir à ce sujet la documentation de Django `_. -Pour les utiliser, vous devez "charger" le module dans le gabarit à l'aide du code suivant: +La majorité de ces modules proposent aussi des fonctions proposant les même fonctionnalités depuis le reste du code +Python. -.. sourcecode:: html - - {% load nom_du_module %} - -La majorité de ces modules proposent aussi des fonctions permettant les mêmes fonctionnalités depuis le reste du code Python. +append_to_get +============= -Vous retrouverez si dessous une liste des modules et chacune des fonctions disponibles dans ceux-ci. - -Le module ``append_to_get`` -=========================== - -Ce module défini l'élément ``append_to_get``, qui permet de rajouter des paramètres à la requête ``GET`` courante. Par exemple, sur une page -``module/toto``, le code de gabarit suivant : +L'élément ``append_to_get`` permet de rajouter des paramètres à la requête ``GET`` courante. Par exemple, sur une page +``module/toto``, le code de template suivant : .. sourcecode:: html @@ -65,7 +58,7 @@ Ce filtre formate une date au format ``DateTime`` destiné à être affiché sur .. sourcecode:: html {% load date %} - {{ date | format_date}} + {{ date|format_date }} ``tooltip_date`` ---------------- @@ -80,13 +73,13 @@ Formate une date au format *Nombre de seconde depuis Epoch* en un élément lisi .. sourcecode:: html {% load date %} - {{ date_epoch | humane_time}} + {{ date_epoch|humane_time }} sera rendu : .. sourcecode:: html - 01 Jan 1970, 01:00:42 + jeudi 01 janvier 1970 à 00h00 Si le contenu de ``date_epoch`` etait de ``42``. @@ -167,13 +160,24 @@ Il permet de rendre un texte Markdown en HTML. Il y a deux commandes : Markdown vers Markdown ---------------------- -Ces élements sont utilisés dans le cadre de la transformation du Markdown avant d'être traité par ``Pandoc`` lors de la +Ces élements sont utilisés dans le cadre de la transformation du markdown avant d'être traité par ``Pandoc`` lors de la génération des fichiers PDF et EPUB des tutos : - ``decale_header_1`` : Décale les titres de 1 niveau (un titre de niveau 1 devient un titre de niveau 2, etc.) - ``decale_header_2`` : Décale les titres de 2 niveaux (un titre de niveau 1 devient un titre de niveau 3, etc.) - ``decale_header_3`` : Décale les titres de 3 niveaux (un titre de niveau 1 devient un titre de niveau 4, etc.) +Le module ``htmldiff`` +========================= + +Ce module définit le tag ``htmldiff`` qui affiche la différence entre deux chaînes de caractères, en utilisant `difflib (en) `__. Le code généré est un tableau HTML à l'intérieur d'une div. Il est employé pour afficher le *diff* des tutoriels et des articles. + +.. sourcecode:: html + + {% load htmldiff %} + {% htmldiff "Hello world!" "Hello world!!!" %} + {% htmldiff "Hello Pierre!" "Hello Peter!" %} + Le module ``interventions`` =========================== @@ -371,36 +375,7 @@ Par exemple, le code suivant appliquera la classe "voted" si le message a reçu {{ message.dislike }} -où ``profile_user`` est le profil (objet ``Profile``) d'un utilisateur et ``message`` est un objet de type ``Post`` (qu'il s'agisse d'un *post* de forum, ou d'un commentaire dans un article ou tutoriel, dont les implémentations different légèrement). Ce *templatetag* est employé dans la partie affichant les réponses. - -Le module ``repo_reader`` -========================= - -Il est employé pour afficher le *diff* des tutoriels et articles. - -``repo_blob`` -------------- - -Ce filtre est basé sur l'utilisation de la librairie `GitPython (en) `__ pour lire un dépôt Git et en extraire des informations. Il récupère le contenu d'un fichier donné dans le dépôt Git. Par exemple, le code suivant lit un fichier et en récupère le texte : - -.. sourcecode:: html - - {% load repo_reader %} - {% with add_next=add.b_blob|repo_blob %} - ... - {% endwith %} - -``diff_text`` -------------- - -Ce filtre affiche la différence entre deux chaines de caractères, en utilisant `difflib (en) `__. Ainsi, le code suivant affiche la différence entre ``maj_prev`` et ``maj_next`` : - -.. sourcecode:: html - - {% load repo_reader %} - {{ maj_prev|diff_text:maj_next|safe }} - -À noter que le résultat de ce filtre est directement en HTML, d'où l'utilisation ici de ``safe``. +où ``profile_user`` est le profil (objet ``Profile``) d'un utilisateur et ``message`` est un objet de type ``Post`` (qu'il s'agisse d'un *post* de forum, ou d'un commentaire dans un article ou tutoriel, dont les implémentations diffèrent légèrement). Ce *templatetag* est employé dans la partie affichant les réponses. Le module ``roman`` =================== @@ -412,7 +387,7 @@ Défini le filtre ``roman``, qui transforme un nombre entier en chiffre romain, {% load roman %} {{ 453|roman }} -affichera ``CDLIII``, qui est bien la facon d'écrire 453 en chiffres romain. +affichera ``CDLIII``, qui est bien la façon d'écrire 453 en chiffres romain. Le module ``set`` ================= @@ -476,22 +451,22 @@ où, - ``top.tags`` contient une liste des 5 *tags* les plus utilisés, qui sont des objets de type ``Tag`` (`voir le détail de l'implémentation de cet objet ici <../back-end-code/utils.html#zds.utils.models.Tag>`__). -``top_categories_tuto`` et ``top_categories_articles`` ------------------------------------------------------- +``top_categories_content`` +-------------------------- -Ces filtres renvoient une liste des catégories utilisées dans les articles/tutoriels publiés. +Ce filtres renvoit une liste des catégories utilisées dans les articles/tutoriels publiés. Par exemple, pour les tutoriels, on retrouvera le code suivant: .. sourcecode:: html - {% with categories=user|top_categories_tuto %} + {% with categories="TUTORIAL"|top_categories_tuto %} {% for title, subcats in categories.items %} ... {% endfor %} {% endwith %} -où ``categories`` est un dictionnaire contenant le nom de la catégorie (ici ``title``) et une liste des sous-catégories correspondantes, c'est-à-dire une liste d'objets de type ``CategorySubCategory`` (`voir le détail de l'implémentation de cet objet ici <../back-end-code/utils.html#zds.utils.models.CategorySubCategory>`__). +où ``categories`` est un dictionnaire contenant le nom de la catégorie (ici ``title``) et une liste des sous-catégories correspondantes (ici ``subcats``), c'est-à-dire un *tuple* de la forme ``titre, slug`` ``auth_forum`` -------------- @@ -505,3 +480,73 @@ Par exemple, le code suivant affichera le lien vers le forum uniquement si celui {% if forum|auth_forum:user %} {{ forum.title }} {% endif %} + +Le module ``feminize`` +====================== + +Permet de générer les déterminants et pronoms adéquats en fonction du mot suivant dynamiquement généré. Typiquement +ce templatetag est utile dans le cas de la hiérarchie des tutoriels où vous pouvez avoir *"une partie"* ou *"un chapitre"*. + +Ce templatetag est basé sur deux dictionnaires de mots : le premier qui associe le déterminant masculin à son homologue +féminin est le second qui associe un mot à un booléen qui indique s'il est féminin ``True`` ou masculin ``False``. + +Exemple : + +.. sourcecode:: html + + + {% load feminize %} + {{ "le"|feminize:"partie" }} partie + +.. attention:: + + le templatetag ``feminize`` est internationalisé. Il est également **sensible à la casse**. + +Le module ``times`` +=================== + +Permet de générer une liste de nombre pour itérer dessus, utile dans les boucles. + +Exemple : + +.. sourcecode:: html + + {% load times %} + {% for i in 25|times %} + je suis dans l'itération {{ i }} + {% endfor %} + +Le module ``target_tree`` +========================= + +Ce module défini un *templatetag* utilisé dans le module de tutoriel (v2) dans le but de générer la hiérarchie des tutos et l'arbre +des déplacements possibles d'un élément. Il s'agit d'un wrapper autour de ``zds.tutorialv2.utils.get_target_tagged_tree``. + +Exemple : + +.. sourcecode:: html + + {% load target_tree %} + {% for element in child|target_tree %} + + {% endfor %} + +le module ``url_category`` +========================== + +Ce module défini un *templatetag* permetant d'accéder à l'url des listes de tutoriels et articles filtrés par tag. Il est employé pour l'affichage des *tags* des tutoriels et articles. + +Exemple : + +.. sourcecode:: html + + {% if content.subcategory.all|length > 0 %} + + {% endif %} \ No newline at end of file diff --git a/doc/source/images/tutorial/import-archive.png b/doc/source/images/tutorial/import-archive.png index c8a1aa50e3..6afa2d6bf9 100644 Binary files a/doc/source/images/tutorial/import-archive.png and b/doc/source/images/tutorial/import-archive.png differ diff --git a/doc/source/install/deploy-in-production.rst b/doc/source/install/deploy-in-production.rst index 49683878f3..2fb450e171 100644 --- a/doc/source/install/deploy-in-production.rst +++ b/doc/source/install/deploy-in-production.rst @@ -585,15 +585,17 @@ Il est possible de personnaliser ZdS pour n'importe quel site communautaire de p 'email_contact': u"communication@zestedesavoir.com", 'email_noreply': u"noreply@zestedesavoir.com", 'repository': u"https://github.com/zestedesavoir/zds-site", + 'bugtracker': u"https://github.com/zestedesavoir/zds-site/issues", + 'forum_feedback_users': u"/forums/communaute/bug-suggestions/", + 'contribute_link': u"https://github.com/zestedesavoir/zds-site/blob/dev/CONTRIBUTING.md", 'short_description': u"", 'long_description': u"Zeste de Savoir est un site de partage de connaissances " u"sur lequel vous trouverez des tutoriels de tous niveaux, " u"des articles et des forums d'entraide animés par et pour " u"la communauté.", - 'year': u"2014", 'association': { 'name': u"Zeste de Savoir", - 'fee': u"30 €", + 'fee': u"20 €", 'email': u"association@zestedesavoir.com", 'email_ca': u"ca-zeste-de-savoir@googlegroups.com" }, @@ -602,7 +604,7 @@ Il est possible de personnaliser ZdS pour n'importe quel site communautaire de p 'code': u"CC-BY", 'title': u"Creative Commons License", 'description': u"Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - " - u"Partage dans les Mêmes Conditions 4.0 International.", + u"Partage dans les Mêmes Conditions 4.0 International.", 'url_image': u"http://i.creativecommons.org/l/by-nc-sa/4.0/80x15.png", 'url_license': u"http://creativecommons.org/licenses/by-nc-sa/4.0/", 'author': u"MaxRoyo" @@ -618,9 +620,10 @@ Il est possible de personnaliser ZdS pour n'importe quel site communautaire de p 'code': u"GPL v3", 'url_license': u"http://www.gnu.org/licenses/gpl-3.0.html", 'provider_name': u"Progdupeupl", - 'provider_url': u"http://pdp.microjoe.org/", + 'provider_url': u"http://pdp.microjoe.org", }, - 'licence_info_title': u'http://zestedesavoir.com/tutoriels/281/le-droit-dauteur-creative-commons-et-les-licences-sur-zeste-de-savoir/', + 'licence_info_title': u'http://zestedesavoir.com/tutoriels/281/le-droit-dauteur-creative-commons-et-les-lic' + u'ences-sur-zeste-de-savoir/', 'licence_info_link': u'Le droit d\'auteur, Creative Commons et les licences sur Zeste de Savoir' }, 'hosting': { @@ -645,15 +648,32 @@ Il est possible de personnaliser ZdS pour n'importe quel site communautaire de p 'image_max_size': 1024 * 1024, }, 'article': { - 'home_number': 5, - 'repo_path': os.path.join(SITE_ROOT, 'articles-data') + 'home_number': 4, + 'repo_path': os.path.join(BASE_DIR, 'articles-data') }, 'tutorial': { - 'repo_path': os.path.join(SITE_ROOT, 'tutoriels-private'), - 'repo_public_path': os.path.join(SITE_ROOT, 'tutoriels-public'), + 'repo_path': os.path.join(BASE_DIR, 'tutoriels-private'), + 'repo_public_path': os.path.join(BASE_DIR, 'tutoriels-public'), 'default_license_pk': 7, 'home_number': 5, - 'helps_per_page': 20 + 'helps_per_page': 20, + 'content_per_page': 50, + 'feed_length': 5 + }, + 'content': { + 'repo_private_path': os.path.join(BASE_DIR, 'contents-private'), + 'repo_public_path': os.path.join(BASE_DIR, 'contents-public'), + 'extra_contents_dirname': 'extra_contents', + 'max_tree_depth': 3, + 'default_license_pk': 7, + 'content_per_page': 50, + 'notes_per_page': 25, + 'helps_per_page': 20, + 'feed_length': 5, + 'user_page_number': 5, + 'default_image': os.path.join(BASE_DIR, "fixtures", "noir_black.png"), + 'import_image_prefix': 'archive', + 'build_pdf_when_published': True }, 'forum': { 'posts_per_page': 21, @@ -664,8 +684,17 @@ Il est possible de personnaliser ZdS pour n'importe quel site communautaire de p 'beta_forum_id': 1, 'max_post_length': 1000000, 'top_tag_max': 5, + 'home_number': 5, + 'old_post_limit_days': 90 + }, + 'topic': { + 'home_number': 6, + }, + 'featured_resource': { + 'featured_per_page': 100, + 'home_number': 5, }, - 'paginator':{ + 'paginator': { 'folding_limit': 4 } } diff --git a/doc/source/install/install-solr.rst b/doc/source/install/install-solr.rst index df97654902..db30a89366 100644 --- a/doc/source/install/install-solr.rst +++ b/doc/source/install/install-solr.rst @@ -60,8 +60,15 @@ Une question vous est alors posée : Répondez "y". Quand c'est fait, l'index est créé et vous avez une recherche fonctionnelle. +Pour mettre à jour les informations sur les contenus (tutoriels et articles), il vous faudrait utiliser la commande: + +.. code:: bash + + python manage.py index_content --only-flagged + Pour mettre à jour un index existant, la commande est : .. code:: bash - python manage.py update_index \ No newline at end of file + python manage.py update_index + diff --git a/doc/source/utils/fixture_loaders.rst b/doc/source/utils/fixture_loaders.rst index e8a5109a14..6c4c1b63c1 100644 --- a/doc/source/utils/fixture_loaders.rst +++ b/doc/source/utils/fixture_loaders.rst @@ -104,15 +104,13 @@ Ce coefficient sera à multiplier par le *coefficient de taille* dirrigé par : +---------------------------------+-----------------------------------+-----------------------------+ |staff |Profile (avec droit de staff) |3 | +---------------------------------+-----------------------------------+-----------------------------+ -|gallery |Gallery/UserGallery(au hasard) |3 (par user) | +|gallery |Gallery/UserGallery (au hasard) |3 (par user) | | +-----------------------------------+-----------------------------+ | |Image |5 (par gallery) | +---------------------------------+-----------------------------------+-----------------------------+ |category_forum |forum.Category |8 | +---------------------------------+-----------------------------------+-----------------------------+ -|category_content |Licence |"CB-BY", "CC-BY-ND", | -| | |"CC-BY-ND-SA", "CC-BY-SA", | -| | |"CC","CC-BY-IO","Tout-Droits"| +|category_content |Licence | Plusieurs [#lic]_ | | +-----------------------------------+-----------------------------+ | |utils.Category |5 | | +-----------------------------------+-----------------------------+ @@ -122,17 +120,26 @@ Ce coefficient sera à multiplier par le *coefficient de taille* dirrigé par : +---------------------------------+-----------------------------------+-----------------------------+ |tag |Tag |50 | +---------------------------------+-----------------------------------+-----------------------------+ -|topic |Topic (dont sticky et locked) |20 | +|topic |Topic (dont *sticky* et *locked*) |20 | +---------------------------------+-----------------------------------+-----------------------------+ -|post |Post |10(en moyenne par topic) | +|post |Post |10 (par topic) [#moy]_ | +---------------------------------+-----------------------------------+-----------------------------+ -|article |Article(40% en validation, 20% pub)|10 | +|comment |ContentReaction |10 (par contenu) [#moy]_ | +---------------------------------+-----------------------------------+-----------------------------+ -|reaction |Reaction |20(en moyenne par article | +|tutorial2 et article2 |PublishableContent [#cv2]_ |10 | +---------------------------------+-----------------------------------+-----------------------------+ -|tutorial |Tutorial |10 | -| |40% en validation, 30% pub | | -| |Principalement des bigtuto | | +|article (**déprécié**) [#dep]_ |Article [#art]_ |10 | +---------------------------------+-----------------------------------+-----------------------------+ -|note |Note |20(en moyenne par tutoriel) | +|reaction (**déprécié**) [#dep]_ |Reaction |20 (par article) [#moy]_ | +---------------------------------+-----------------------------------+-----------------------------+ +|tutorial (**déprécié**) [#dep]_ |Tutorial [#tut]_ |10 | ++---------------------------------+-----------------------------------+-----------------------------+ +|note (**déprécié**) [#dep]_ |Note |20 (par tutoriel) [#moy]_ | ++---------------------------------+-----------------------------------+-----------------------------+ + +.. [#lic] Les licences suivantes seront créée : "CB-BY", "CC-BY-ND", "CC-BY-ND-SA", "CC-BY-SA", "CC", "CC-BY-IO" et "Tout-Droits" +.. [#cv2] C'est à dire 60% en validation (dont 20% réservés) et 30% publiés. S'il sagit de tutoriels, 50% de petits, 30% de moyen et 20% de *bigs*. +.. [#moy] Ce nombre est une moyenne, le nombre réel est choisi au hasard autour de cette moyenne +.. [#art] 40% en validation, 20% publiés +.. [#tut] 40% en validation, 30% publiés. Principalement des *bigtutos*. +.. [#dep] Ces commandes sont ammenées à disparaitre avec l'implémentation de la ZEP-12 \ No newline at end of file diff --git a/doc/source/utils/generate_pdf.rst b/doc/source/utils/generate_pdf.rst new file mode 100644 index 0000000000..0fb3d89048 --- /dev/null +++ b/doc/source/utils/generate_pdf.rst @@ -0,0 +1,27 @@ +======================================== +Générer les PDFs des différents contenus +======================================== + +Vous avez la possibilité de générer le (ou les) PDF(s) d'un (ou plusieurs) contenu(s) déjà publié(s) en une commande : + +.. sourcecode:: bash + + python manage.py generate_pdf + +Les PDFs de tout les contenus publiés seront alors (re)générés. + +.. attention:: + + Cette commande supprime le PDF déjà généré. + +Vous pouvez préciser une liste de contenus dont les PDF doivent être (re)généré en employant l'argument ``id`` (ou ``ids``). Par exemple, pour générer les PDFs des contenus dont l'id est ``125``, ``142`` et ``56`` if faut mettre: + +.. sourcecode:: bash + + python manage.py generate_pdf id=125,142,56 + +Seuls ces PDFs seront alors (re)générés. + +.. attention:: + + Les ``id`` qui ne seraient pas valides sont automatiquement éliminés. Si aucun n'est valide, la commande ne fait rien. diff --git a/doc/source/utils/pdf-generator.rst b/doc/source/utils/pdf-generator.rst deleted file mode 100644 index 37ec9aa2fd..0000000000 --- a/doc/source/utils/pdf-generator.rst +++ /dev/null @@ -1,11 +0,0 @@ -============================== -Générer les PDFs des tutoriels -============================== - -Vous avez la possibilité de générer le (ou les) PDF(s) d'un (ou plusieurs) tutoriel(s) déjà publié(s) en une commande : - -.. sourcecode:: bash - - python manage.py pdf_generator - -Pour préciser le (ou les) tutoriel(s) à générer, il suffit de rajouter l'argument ``id``. Par exemple, pour générer les pdfs des tutoriels dont l'id est ``125``, ``142``, et ``56`` if faut mettre ``id=125,142,56``. \ No newline at end of file diff --git a/fixtures/advanced/articles.yaml b/fixtures/advanced/articles.yaml new file mode 100644 index 0000000000..aef446e59c --- /dev/null +++ b/fixtures/advanced/articles.yaml @@ -0,0 +1,10 @@ +- factory: zds.article.factories.PublishedArticleFactory + fields: + title: "Article court" + authors: [3] + light: True +- factory: zds.article.factories.PublishedArticleFactory + fields: + title: "Article long" + authors: [1,3] + light: False diff --git a/fixtures/tuto/BadArchive.zip b/fixtures/tuto/BadArchive.zip new file mode 100644 index 0000000000..894cfb4412 Binary files /dev/null and b/fixtures/tuto/BadArchive.zip differ diff --git a/fixtures/tuto/article_v1/manifest.json b/fixtures/tuto/article_v1/manifest.json new file mode 100644 index 0000000000..966b2afed0 --- /dev/null +++ b/fixtures/tuto/article_v1/manifest.json @@ -0,0 +1,7 @@ +{ + "title": "Microsoft et l'open source, comme chien et chat", + "description": "A moins que ça ne soit comme chat et chien", + "type": "article", + "text": "text.md", + "licence": "CC BY" +} \ No newline at end of file diff --git a/fixtures/tuto/article_v1/text.md b/fixtures/tuto/article_v1/text.md new file mode 100644 index 0000000000..94e3116d84 --- /dev/null +++ b/fixtures/tuto/article_v1/text.md @@ -0,0 +1,125 @@ +Le 12 novembre 2014, Microsoft a annoncé[^annonce] qu'une majeure partie du framework .NET serait rendue public sous [licence MIT](https://fr.wikipedia.org/wiki/Licence_MIT) afin de promouvoir le développement multiplateforme de site web sous ASP.NET. + +Cette nouvelle a été rapidement suivie d'une annonce de la transformation de Visual Studio Professionnal en [Visual Studio Community Edition](http://www.visualstudio.com/en-us/products/visual-studio-community-vs) sous certaines conditions. + +Avant d'en arriver là, Microsoft est passé par plusieurs étapes dans sa réflexion, penchons-nous sur ces dernières. + +# "Les promoteurs du libre? Des communistes" + +Alors qu'il était encore PDG de Microsoft, Bill Gates a dû faire face à la montée en puissance des logiciels libres et open source tels que OpenOffice en même temps que le piratage des licences de ses logiciels. Alors qu'il donnait une interview, il estimait alors : + +>Il existe une nouvelle sorte de communistes modernes qui veut être exemptée de taxes rémunérant les musiciens, les cinéastes et les éditeurs de logiciels. +Source: Bill Gates + +Depuis sa fondation, Microsoft base son *business model* sur la vente de logiciels. Rapidement la branche Windows a été la plus rentable de toutes suivie de la branche Office. + +L'arrivée de Azure (cloud de Microsoft) et des consoles XBox n'a pas vraiment remis en cause ce modèle qui n'a connu qu'un seul et unique trimestre déficitaire dans **toute son histoire** ! D'après S.Balmer, cela était dû à une forte dépréciation des actifs financiers liés à une filiale spécialisée dans la publicité qui était sensée concurrencer Google. + +Depuis sa création, en 1975, Microsoft n'aura connu que trois PDG. L'histoire commence avec Bill Gates amenant la firme qu'il a co-fondée avec S.Allen au sommet de sa gloire jusqu'aux premiers procès antitrust des années 90. + +Suite à ces premiers procès antitrust, et désireux de se concentrer sur son engagement humanitaire avec sa femme, B.Gates cède la direction de Microsoft à Steeve Balmer qui y restera jusqu'en 2014 lorsqu'il cèdera sa place à Satya Nadella. + +Balmer sera le premier à infléchir la stratégie de Microsoft vers des cycles de développement plus rapides[^rapideIE] et à tenter de faire venir un maximum de développeurs vers les technologies Microsoft. + +L'arrivée de Satya Nadella à la tête de la firme de Redmond permettra de concrétiser cette inflexion pour faire entrer Microsoft dans une ère plus libre et ouverte à l'open source, bien loin des considérations de B.Gates. + +# .NET sur Linux, l'histoire mouvementée de Mono + +En 2002, Microsoft décide de réunir les différentes technologies qu'il édite dans un cadre commun qu'il appelle le Framework .NET. Rapidement, les différents langages de Microsoft tels que VB ou Visual C++ seront intégrés à cette plateforme. + +La plateforme .NET se base sur une machine virtuelle comparable à la Java Virtual Machine. Microsoft l'appelle CLI (Common Language Infrastructure) et nommera le langage intermédiaire généré par cette machine virtuelle CLR (Common Language Runtime). L'idée est double : + +- Proposer, comme pour Java, un environnement de développement multiplateforme (les programmes écrits grâce à la plateforme .NET sont dès le départ disponibles pour Windows 98, 2000, NT, XP et leurs équivalents côté serveur). +- Permettre d'unifier les bibliothèques développées avec les différentes technologies. Ainsi vous pouvez développer un outil en Visual Basic .NET qui utilisera des bibliothèques écrites en Visual C++ ou plus tard en C#. Comme toutes ces technologies utilisent .NET, vous n'avez pas besoin d'adapter quoi que ce soit! + +Rapidement, les développeurs remarqueront que le premier objectif n'est pas vraiment atteint : seules les plateformes Windows sont supportées. Une équipe se rassemblera alors autour du projet [Mono](http://www.mono-project.com/), qui aura pour but d'écrire un interpréteur de CLR pour Linux et Mac. + +Heureusement pour les développeurs de Mono, la CLI sera normalisée en majeure partie sous le doux nom de [ECMA335](http://www.ecma-international.org/publications/standards/Ecma-335.htm). Et Mono tentera de coller à cette norme. + +Afin de s'assurer que l'outil sera utilisé par un maximum de personne, le créateur de Mono, Miguel de Icaza, fonde la société Ximian afin d'assurer un support client fiable aux utilisateurs de Mono. + +Pourtant, dès le commencement, les retards de développement se font sentir[^mono]. Microsoft a un rythme de développement assez élevé et si .NET 1.0 a été publié en 2002, l'année a vu le .NET4.5.2 naître, très loin devant cette première norme. + +Microsoft sera durant ces années très peu propice à partager son savoir faire avec l'équipe de Mono, ne participant à son développement que lorsque l'image même de .NET menace d'être écornée si Mono prend trop de retard. + +En 2003, peu de temps après sa création, la société Ximian qui était le principal mainteneur de Mono est rachetée par Novell. Avec ce rachat, le développement de Mono va sensiblement s'accélérer. + +En 2006, Microsoft attaque Novell, la société qui sponsorisait le développement de Mono à cette époque, pour violation de brevet. Néanmoins, au vu de la popularité grandissante des outils Mono, notamment utilisés par SecondLife[^scripting], Microsoft accordera à Novel le droit d'utiliser les technologies développées pour Mono qui imitaient les brevets .NET[^patent]. + +En 2007, un événement va bouleverser toute la stratégie de Microsoft : Apple sort son IPhone, première version. Le "virage du mobile" doit alors être négocié et ni Windows Mobile, ni Windows Phone 7.5 n'y parviendront. + +![Ventes de mobiles en 2011->2013 (gartner/eco-conscient)](http://www.eco-conscient.com/wp-content/uploads/cache/2013/11/gartner-os-smartphone-vente-quarter/558378059/2494269678.png) + +L'avenir de Mono va se jouer en 2011 lorsqu'une firme, nommée Xamarin est créée suite au rachat de Novell par un fond d'investissement, [AtacheMate](http://www.channelnews.fr/actu-societes/fournisseurs/8537-rachat-de-novell-par-attachmate-ce-quen-pensent-les-partenaires-francais.html). +Cette société a pour but de créer des applications *natives* sur *toutes* les plateformes présentes sur le marché (IOS, Android, Windows Phone 7.5 puis 8) à partir d'un code C# compilé depuis Mono justement. + +L'année 2011 sera d'autant plus importante qu'Unity3D, le [moteur de création de jeu vidéo ](http://openclassrooms.com/courses/realisez-votre-premier-jeu-video-avec-unity) intégrera Javascript et C# comme langage de *scripting*. L'intégration de C# se fera via Mono pour qu'Unity3D soit portable sur les différents OS. De même Sony utilisera C# et Mono pour la PS Suite. + +Voyant dans cette société un potentiel énorme de création d'applications pour leur *store* alors peu fourni en comparaison avec la concurrence, Microsoft fait de Xamarin un de ses principaux partenaires dans le développement mobile. Pour la première fois, le projet Mono est soutenu officiellement par Microsoft, ce qui lui permet de voir s'ouvrir de nouvelles portes. + +Pour autant, Mono n'assure pas un support complet de ce qu'on peut trouver dans la plateforme .NET. En effet, le support du WPF n'est pas assuré. Microsoft n'a toujours pas donné les sources de cette bibliothèque graphique et la garde pour son propre environnement. De ce fait, Mono se cantonne au vieillissant WinForm qu'elle implémente grâce à [GTK#](https://github.com/mono/gtk-sharp). + +# Le prix des licences en baisse, le web vers le libre + +Le premier pas vers une intégration bien plus conséquente du libre dans le développement de logiciels utilisant .NET fut la création du gestionnaire de package NuGet. Installable en tant que plugin depuis Visual Studio 2010, il est intégré par défaut depuis la version 2012. + +Ce gestionnaire de package, à l'instar de [pip](http://sametmax.com/votre-python-aime-les-pip/) pour python, [npm](https://www.npmjs.org/) pour JS ou [Maven](http://maven.apache.org/) pour Java, permet de résoudre les dépendances dans vos projets mais aussi de vérifier la compatibilité avec les licences. + +![Exemple d'installation d'un package](/media/galleries/304/5fcde39a-1dfe-427d-b232-e430e888f888.png.960x960_q85.jpg) + +Lorsque vous créez une bibliothèque de code, le partage de cette bibliothèque, la gestion de ses versions peut facilement se faire sur un dépôt NuGet privé, sur [nuget.org](http://nuget.org) ou sur un miroir personnel puisque le code de nuget.org est lui même [libre](https://github.com/NuGet/NuGetGallery). L'exploration de NuGet ou d'un de ses miroirs étant fortement facilitée par la mise en place d'une [API REST](https://www.nuget.org/api/v2/). + +Depuis l'arrivée de Satya Nadella à la tête de Microsoft, une stratégie assumée d'ouverture se met en place. Bien que le PDG de la firme de Redmond soit encore parfois accusé d'utiliser la célèbre stratégie des 3E "Embrace Extend Extinguish"[^troise], plusieurs actions sont venues rassurer le monde du libre. + +Cette stratégie commerciale s'appuie sur un changement radical de politique de vente : toute licence OEM[^OEM] vendue sur un *device* dont l'écran fait moins de 10 pouces est gratuite. +De plus, la direction de Microsoft adhère au constat qu'aujourd'hui, l'open source est passé de ["toléré" à "prévu"](http://opensource.com/business/14/10/interview-dwight-merriman-mongodb). + +Dans cette optique, le développement de [ASP.NET MVC](http://www.asp.net/mvc) ainsi que d'autres modules tels que [SignalR](http://signalr.net/) a dès le départ été rendus [open](http://aspnet.codeplex.com/SourceControl/latest) [source](https://github.com/SignalR/SignalR). + +![SignalR, le framework de temps réel pour ASP.NET](http://blogs.microsoft.co.il/gadib/wp-content/uploads/sites/1318/2014/09/signalr.png) + +![SignalR, le framework de temps réel pour ASP.NET](http://4.bp.blogspot.com/-3ZX-o-6ejIA/UVgT5kKF39I/AAAAAAAAADU/c8vXKZypfhE/s1600/SignalR+Fallback2.png) + +# De la .NET Foundation à la libération du .NET Core + +Nous arrivons donc en 2014. Microsoft publie Windows 8.1 suite à l'échec relatif de la version 8.0, de même les WindowsPhone équipés de la version 8 de toutes les gammes sont désignés comme éligibles à WindowsPhone 8.1. Microsoft annonce la création des [applications universelles](http://www.gizmodo.fr/2014/04/03/applications-universelles-microsoft.html). L'expérience de la mise en open source de ASP.NET MVC s'étant révélée positive, Microsoft décide de créer en collaboration avec Xamarin la .NET Foundation. + +Cette fondation réunit les [projets](http://www.dotnetfoundation.org/projects) considérés comme majeurs par ses fondateurs, Xamarin en pilotant plus de dix ! + +L'un des plus gros projets de cette fondation est la création d'un compilateur open source utilisant au maximum les technologies de [compilation à la volée](http://fr.wikipedia.org/wiki/Compilation_%C3%A0_la_vol%C3%A9e) pour : + +- Augmenter fortement les performances +- Améliorer l'efficacité, la justesse et l'intégration des systèmes d'auto complétion dans les IDE +- Permettre par la suite d'envoyer un programme utilisant .NET qui n'a pas besoin de l'installation complète du framework pour être partagé car le code sera natif. + +C'est à partir de la .NET Foundation que la stratégie d'ouverture de Microsoft passe désormais et, il y a peu, une partie du framework a été complètement ouverte et publiée sous licence MIT, soit l'une des plus permissives ! + +Pour comprendre quelles parties ont été publiées, le mieux reste de vous fournir le [schéma officiel](http://blogs.msdn.com/b/dotnet/archive/2014/11/12/net-core-is-open-source.aspx) : + +![Architecture du .NET Core](http://blogs.msdn.com/resized-image.ashx/__size/550x0/__key/communityserver-blogs-components-weblogfiles/00-00-01-12-34/2577.DotNet2015.png) + +Ce qu'il faut comprendre c'est que Microsoft lie toujours très fortement ses bibliothèques graphiques (WinForm et WPF) à son système d'exploitation et refuse donc d'en libérer les sources pour aider les équipes de Mono. Le but est clairement d'éviter que les distributions Linux puissent copier l'identité visuelle que l'on trouve depuis les version 2010 d'office et 2012 de Visual Studio. + +# Sources principales + +- Annonce officielle de Microsoft ([en](http://blogs.msdn.com/b/dotnet/archive/2014/11/12/net-core-is-open-source.aspx)) +- Détail des apports ([fr, NextINpact](http://www.nextinpact.com/news/90895-microsoft-ouvre-net-a-open-source-et-propose-visual-studio-2013-gratuit.htm)) +- Fiche Wikipédia de [Microsoft](http://fr.wikipedia.org/wiki/Microsoft) +- Le blog de Xamarin ([en](http://blog.xamarin.com/xamarin-and-the-.net-foundation/)) +- Article de developpez.com sur Roslyn ([fr](www.developpez.com/actu/71892/Le-prochain-Visual-Studio-se-devoile-Microsoft-publie-la-preversion-de-Visual-Studio-14-avec-Roslyn-ASP-NET-vNext-et-le-support-de-Cplusplus-11-14/)) +- Le github de .NET ([en](https://github.com/dotnet)) + + +[^annonce]: [source officielle](http://blogs.msdn.com/b/dotnet/archive/2014/11/12/net-core-is-open-source.aspx) +[^rapideIE]: Internet Explorer, par exemple sort désormais une version majeure par an plutôt que tous les 2 ou 3 ans (IE6, comme XP ayant été un ovni)! +[^mono]: Aujourd'hui encore le support dit ["Everything in .NET 4.5 except WPF, WWF, and with limited WCF and limited ASP.NET 4.5 async stack."](http://www.mono-project.com/docs/about-mono/compatibility/) +[^patent]: Ce qui a donné lieu à plusieurs [montées de bouclier](http://www.osnews.com/story/21586/Mono_Moonlight_Patent_Encumbered_Or_Not_) dans le monde du libre car seuls Novel et ses clients étaient alors concernés par cet accord. +[^scripting]: Plus précisément pour le scripting des mods de SecondLife +[^OEM]: L'acronyme originel signifie *[Original Equipment Manufacturer](http://fr.wikipedia.org/wiki/Fabricant_d%27%C3%A9quipement_d%27origine)* et désigne dans l'industrie les pièces détachées vendues dans des packages minimalistes. Les licences "OEM" ont été mises en place pour limiter la réutilisation des logiciels préinstallés dans les ordinateurs grand public. Si vous changez l'ordinateur, vous n'avez pas le droit d'utiliser les logiciels OEM de l'ancien. Cette pratique est fortement décriée dans le monde du libre. +[^troise]: Adopte, étends et extermine, la stratégie d'introduction des produits par [Microsoft](http://fr.wikipedia.org/wiki/Embrace,_extend_and_extinguish) + +*[PDG]: Président Directeur Général, il dirige le conseil d'administration (président) et l'entreprise au quotidien (directeur général). +*[CLI]: Common Language Infrastructure +*[CLR]: Common Language Runtime +*[.NET]: A prononcer "dote nette", bien qu'aucun mariage ne soit en jeu. +*[WPF]: Windows Presentation Foundation : un framework qui permet de créer des interfaces graphiques modernes qui s'appuie beaucoup sur la programmation *asynchrone* \ No newline at end of file diff --git a/fixtures/tuto/balise_audio/13428_les-balises-audio-et-video.md b/fixtures/tuto/balise_audio/13428_les-balises-audio-et-video.md new file mode 100644 index 0000000000..6a2c9e2b04 --- /dev/null +++ b/fixtures/tuto/balise_audio/13428_les-balises-audio-et-video.md @@ -0,0 +1,145 @@ +# Les balises audio et vidéo + +Comme vous le savez, en HTML, tout élément est représenté par une balise. `audio` et `video` ne feront pas exception. + +Ces éléments sont de type *block* et sont écrits de la manière suivante : + +```html + +``` + +```html + +``` + +# Les éléments importants de ces balises + +Bien que leurs buts soient différents, ces deux balises sont très similaires dans leur comportement. Finalement, la seule différence entre un contenu audio et vidéo est que le second *peut* inclure le premier (mais pas nécessairement). Dans un cas comme dans l’autre, on souhaite lire une piste et afficher une interface à l’utilisateur pour interagir avec cette dernière. + +Cela nous amène donc au premier attribut indispensable de ces deux balises : la **source**. + +## La source + +Savoir afficher une balise audio/vidéo c’est bien, mais si on ne lui donne rien à afficher, on n’est pas plus avancé ! Il va donc falloir donner une source à afficher. Tout comme pour une image, elle peut être relative ou absolue. Il existe deux moyens pour la spécifier. + +### Via l’attribut `src` + +Là encore, comme pour une image, il suffit de spécifier l’attribut `src` pour donner un lien vers la vidéo ou le flux audio à lire. + +```html + +``` + +### Via la balise `` + +Cependant, il peut être intéressant de proposer plusieurs formats à l’utilisateur. En effet, tous les navigateurs ne savent pas lire tous les formats vidéo. On propose donc la même vidéo dans des formats différents et le navigateur choisira ! Pour cela, on utilise la balise `` *dans* la balise audio/vidéo. + +```html + +``` + +Voici une petite démonstration[^source] de ce dernier cas : + +-> + +!(https://jsfiddle.net/tmjosu22/3/) + +<- + +[[q]] +| Mais c’est pourri, on peut pas lancer la vidéo ! Ça marche pas ! + +En fait si, tout marche très bien, c'est juste que maintenant que nous avons la vidéo, il va falloir la contrôler… + +# Contrôler (simplement) le média + +Maintenant que la vidéo est présente, ajoutons un peu d’interactivité à cette dernière… + +## Les options « natives » + +Les balises multimédia possèdent par défaut quelques attributs bien pratiques. En effet, voici une liste non exhaustive de celles que j'estime être les plus utiles dans l’immédiat : + ++ `controls` : permet de rajouter des boutons de contrôle de lecture standards (lecture/pause, barre de progression, plein-écran…) ; ++ `autoplay` : (plutôt évident…) la lecture est lancée automatiquement dès que la vidéo commence à se charger ; n’en abusez pas, cela peut être assez gênant pour la navigation ; ++ `poster` ^*^ : lien vers une image d'illustration si la vidéo n'est pas disponible à l'adresse spécifiée ; ++ `loop` : relance la lecture quand cette dernière est terminée, encore et encore ; ++ `height` et `width` ^*^ : pour spécifier une hauteur et une largeur au lecteur ; ++ `muted` : coupe le son. + +~* ne s'applique pas à la balise audio~ + +Voici par exemple une vidéo avec des contrôles, dont le son est coupé, qui jouera en boucle et dont la taille a été limitée à 320x240 pixels. + +```html hl_lines="1" + +``` + +-> + +!(https://jsfiddle.net/tmjosu22/4/) + +<- + +Vous savez maintenant afficher une vidéo ou jouer un son ! + +## Encore plus loin, des sous-titres pour les vidéos + +Dans notre monde moderne et international, il arrive que les sous-titres puissent être nécessaires pour offrir le contenu à un plus grand public. Et c’est là que la balise `` intervient. Placée dans une balise vidéo, cette dernière proposera des sous-titres au lecteur. + +La balise *track* a besoin des informations suivantes : + ++ `src` : la source (relative ou absolue) du fichier de sous-titres (au format WebVTT [.vtt](http://dev.w3.org/html5/webvtt/) (WEB Video Text Track)) ; ++ `kind="subtitles"` : pour préciser que l'on parle de sous-titres ; ++ `srclang` : le code international de la langue (en, de, fr…) ; ++ `label` : le nom littéral de la piste de sous-titres. + +Par exemple : + +```html hl_lines="4-5" + +``` + +[[a]] +| À l’heure d’écriture de ce tutoriel, cette balise est encore très peu supportée dans les navigateurs. + +## *Fallback* + +Si l'utilisateur qui visite votre site possède un navigateur un peu *rétro* ou incomplet vis-à-vis des standards du Web, il serait de bon ton de l’avertir que le contenu ne peut être affiché plutôt que de le laisser attendre indéfiniment un média qui n’arrivera jamais. + +Pour cela, il suffit tout simplement d’ajouter du HTML dans la balise média concernée. Si le navigateur ne sait pas interpréter la balise vidéo/audio, alors il ignorera les balises et affichera notre *fallback*. Sinon ce contenu est ignoré car la balise est correctement interprétée. + +```html hl_lines="7-9" + +``` + +[[i]] +| Plutôt que d’afficher du texte, vous pouvez très bien aussi afficher un conteneur Flash en solution de secours pour jouer la vidéo. L’idéal est même de proposer une alternative ET les liens de téléchargement de la vidéo, si la licence de distribution de cette dernière le permet. + +[^source]: Les vidéos d’exemple sont celles du film [*Big Buck Bunny*](https://peach.blender.org/), un film sous licence Creative Commons. \ No newline at end of file diff --git a/fixtures/tuto/balise_audio/13430_interagir-un-peu-plus-avec-les-medias.md b/fixtures/tuto/balise_audio/13430_interagir-un-peu-plus-avec-les-medias.md new file mode 100644 index 0000000000..62ccd8c2bd --- /dev/null +++ b/fixtures/tuto/balise_audio/13430_interagir-un-peu-plus-avec-les-medias.md @@ -0,0 +1,165 @@ +Lorsque l’on a un média, on peut avoir envie d'interagir un peu plus avec que simplement afficher les contrôles de base. Nous ferons alors appel à JavaScript pour rentrer dans les entrailles du lecteur… + +Imaginons que nous voulions proposer un lecteur sans contrôles natifs, mais uniquement avec nos boutons HTML que nous pourrions styliser via du CSS. Il faudrait alors que ces boutons interagissent avec la vidéo correctement. Admettons que nous voulions ajouter les contrôles suivants : + ++ `Lecture` : lit la vidéo ; ++ `Pause` : met la vidéo en pause ; ++ `Stop` : arrête la vidéo ; ++ `-10s` : recule la vidéo de 10 secondes ; ++ `+10s` : avance la vidéo de 10 secondes ; + +## Structure de base + +Voici la structure de base que nous allons respecter : + +```html + + +``` + +```javascript +function lecture() { + // Lit la vidéo +} + +function pause() { + // Met la vidéo en pause +} + +function stop() { + // Arrête la vidéo +} + +function avancer(duree) { + // Avance de 'duree' secondes +} + +function reculer(duree) { + // Recule de 'duree' secondes +} + +function creerBoutons() { + // Crée les boutons de gestion du lecteur +} +``` + +-> + +!(https://jsfiddle.net/tmjosu22/8/) + +<- + +## Mettre nos contrôleurs + +Comme vous pouvez le voir dans le squelette précédent, pour l'instant, aucun bouton personnalisé n’est présent sur notre page et la vidéo possède l’interface par défaut. En effet, nous allons créer les boutons dynamiquement en JavaScript dans la fonction `creerBoutons()` qui sera exécutée à la fin du chargement de la page. De cette manière, un utilisateur désactivant le JavaScript pourra tout de même utiliser le navigateur avec l’interface standard. + +Voici comment nous allons créer nos boutons. Je compte sur vous pour comprendre sans explication, juste avec les commentaires ! ;) + +```javascript +var lecteur; + +function creerBoutons() { + // Crée les boutons de gestion du lecteur + var btnLecture = document.createElement("button"); + var btnPause = document.createElement("button"); + var btnStop = document.createElement("button"); + var btnReculer = document.createElement("button"); + var btnAvancer = document.createElement("button"); + + var controlesBox = document.getElementById("controles"); + lecteur = document.getElementById("mavideo"); + + // Ajoute un peu de texte + btnLecture.textContent = "Lecture"; + btnPause.textContent = "Pause"; + btnStop.textContent = "Stop"; + btnReculer.textContent = "-10s"; + btnAvancer.textContent = "+10s"; + + // Ajoute les boutons à l'interface + controlesBox.appendChild(btnLecture); + controlesBox.appendChild(btnPause); + controlesBox.appendChild(btnStop); + controlesBox.appendChild(btnReculer); + controlesBox.appendChild(btnAvancer); + + // Lie les fonctions aux boutons + btnLecture.addEventListener("click", lecture, false); + btnPause.addEventListener("click", pause, false); + btnStop.addEventListener("click", stop, false); + btnReculer.addEventListener("click", function(){reculer(10)}, false); + btnAvancer.addEventListener("click", function(){avancer(10)}, false); + + // Affiche les nouveaux boutons et supprime l'interface originale + controlesBox.removeAttribute("hidden"); + lecteur.removeAttribute("controls"); +} + +// Crée les boutons lorsque le DOM est chargé +document.addEventListener('DOMContentLoaded', creerBoutons, false); +``` + +-> + +!(https://jsfiddle.net/tmjosu22/11/) + +<- + +## Interagir avec la vidéo + +Maintenant que nous avons un squelette, nous allons devoir faire appel aux propriétés de l'objet vidéo pour interagir avec (son id est « mavideo »). Pour cela, on ira chercher dans la référence de l’élément : [HTMLMediaElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement). Vous y trouverez les attributs accessibles (dont certains ont été vus plus tôt) ainsi que les méthodes que nous pouvons appeler. + +Ainsi nous trouverons par exemple les éléments suivants : + ++ `play()` : pour lire la vidéo ; ++ `pause()` : pour la mettre en pause ; ++ `currentTime` : attribut représentant le minutage actuel de la vidéo (*position* dans la vidéo). + +On va alors pouvoir implémenter les méthodes JavaScript proposées plus tôt ! + +```javascript +function lecture() { + // Lit la vidéo + lecteur.play(); +} + +function pause() { + // Met la vidéo en pause + lecteur.pause(); +} + +function stop() { + // Arrête la vidéo + // On met en pause + lecteur.pause(); + // Et on se remet au départ + lecteur.currentTime = 0; +} + +function avancer(duree) { + // Avance de 'duree' secondes + // On parse en entier pour être sûr d'avoir un nombre + lecteur.currentTime += parseInt(duree); +} + +function reculer(duree) { + // Recule de 'duree' secondes + // On parse en entier pour être sûr d'avoir un nombre + lecteur.currentTime -= parseInt(duree); +} +``` + +-> + +!(https://jsfiddle.net/tmjosu22/15/) + +<- \ No newline at end of file diff --git a/fixtures/tuto/balise_audio/13431_tp-faire-votre-propre-lecteur-multimedia.md b/fixtures/tuto/balise_audio/13431_tp-faire-votre-propre-lecteur-multimedia.md new file mode 100644 index 0000000000..79b4b229e9 --- /dev/null +++ b/fixtures/tuto/balise_audio/13431_tp-faire-votre-propre-lecteur-multimedia.md @@ -0,0 +1,45 @@ +# Consigne + +En guise d’exercice, je vous propose de continuer l’interface que nous avons commencée en rajoutant les boutons suivants. + +## Niveau 1 + ++ `Répéter` : *checkbox* qui fera répéter la vidéo lorsqu'elle se termine si elle est cochée ; ++ `Avancer/Reculer de xx secondes` : un *input* de votre choix pour sélectionner une valeur et deux boutons pour avancer ou reculer. + +## Niveau 2 + +Pour les plus forts : + ++ Une barre de progression pour afficher où la vidéo est rendue dans sa lecture ; ++ Un *input* de type *range* pour : + + afficher où la vidéo est rendue ; + + sélectionner un endroit où aller. + +## Niveau 3 + +Prouvez que vous être le maître des technos Web, rajoutez une belle couche de CSS par dessus tout cela ! + +-> + +Voila, ça fera de quoi vous occuper :pirate: ! + +**BON COURAGE** <- + +# Solution + +Voici un exemple simple de solution « Niveau 1 » : + +-> + +!(https://jsfiddle.net/tmjosu22/16/) + +<- + +Et voila une deuxième démo mettant plus l'accent sur le style que sur les fonctions. + +-> + +!(https://jsfiddle.net/tmjosu22/20/) + +<- \ No newline at end of file diff --git a/fixtures/tuto/balise_audio/conclusion.md b/fixtures/tuto/balise_audio/conclusion.md new file mode 100644 index 0000000000..a4745fee8e --- /dev/null +++ b/fixtures/tuto/balise_audio/conclusion.md @@ -0,0 +1,3 @@ +S’il est encore nécessaire de montrer l’intérêt des balises modernes du HTML5, sachez par exemple que YouTube, le célèbre hébergeur de vidéos, a décidé d’arrêter complètement l'utilisation de Flash pour ses contenus. En effet, maintenant, toutes les vidéos passent par l'utilisation… de la balise `