diff --git a/.travis.yml b/.travis.yml index 6871860237..d13ecc88e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,45 +1,58 @@ git: depth: 1 + language: python + python: - - "2.7" + - 2.7 + notifications: irc: channels: - - "irc.smoothirc.net#zds-dev" + - irc.smoothirc.net#zds-dev skip_join: true cache: - apt + - pip + install: - - "sudo apt-get update" - - "sudo echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections" - - "sudo apt-get install ttf-mscorefonts-installer" - - "sudo apt-get install texlive" - - "sudo apt-get install texlive-xetex" - - "sudo apt-get install texlive-lang-french" - - "sudo apt-get install texlive-latex-extra" - - "sudo mkdir -p ~/cabal/bin" - - "sudo mkdir -p ~/.pandoc" - - "sudo wget -P ~/cabal/bin https://dl.dropboxusercontent.com/u/18967337/pandoc" - - "sudo wget -P ~/.pandoc/templates https://dl.dropboxusercontent.com/u/14517385/dev/default.epub" - - "sudo wget -P ~/.pandoc/templates https://dl.dropboxusercontent.com/u/14517385/dev/default.html" - - "sudo touch ~/.pandoc/epub.css" - - "sudo touch ~/.pandoc/templates/epub.css" - - "sudo chmod u+x,g+x,o+x ~/cabal/bin/pandoc" - - "export PATH=$PATH:~/cabal/bin" - - "sudo ~/cabal/bin/pandoc --version" - - "sudo apt-get --reinstall install -qq language-pack-fr" - - "pip install -r requirements.txt" - - "pip install coveralls" + # APT Stuff + - sudo apt-get update + - sudo echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections + - sudo apt-get install ttf-mscorefonts-installer + - sudo apt-get install texlive + - sudo apt-get install texlive-xetex + - sudo apt-get install texlive-lang-french + - sudo apt-get install texlive-latex-extra + + # Cabal + Pandoc stuff + - sudo mkdir -p ~/cabal/bin + - sudo mkdir -p ~/.pandoc + - sudo wget -P ~/cabal/bin https://dl.dropboxusercontent.com/u/18967337/pandoc + - sudo wget -P ~/.pandoc/templates https://dl.dropboxusercontent.com/u/14517385/dev/default.epub + - sudo wget -P ~/.pandoc/templates https://dl.dropboxusercontent.com/u/14517385/dev/default.html + - sudo touch ~/.pandoc/epub.css + - sudo touch ~/.pandoc/templates/epub.css + - sudo chmod u+x,g+x,o+x ~/cabal/bin/pandoc + - export PATH=$PATH:~/cabal/bin + - sudo ~/cabal/bin/pandoc --version + - sudo apt-get --reinstall install -qq language-pack-fr + + # Python dependencies + - travis_retry pip install -r requirements.txt + - travis_retry pip install coveralls + # NodeJS + NPM stuff - - "sudo add-apt-repository -y ppa:chris-lea/node.js" - - "sudo apt-get -y update" - - "sudo apt-get -y install nodejs" - - "npm install -g bower gulp" - - "npm install" + - sudo add-apt-repository -y ppa:chris-lea/node.js + - sudo apt-get -y update + - sudo apt-get -y install nodejs + - npm install -g bower gulp + - npm install + script: - npm run-script travis - coverage run --source='.' manage.py test + after_success: - coveralls diff --git a/AUTHORS b/AUTHORS index 59f9da9a3b..713c8dc2db 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,12 +4,15 @@ Original code base (pdp / fork on 11-02-2013) : Romain Porte (MicroJoe) <microjo "Zeste de Savoir" code base : - Alex-D (https://github.com/Alex-D) +- AlexandreBroudin (https://github.com/AlexandreBroudin) - artragis (https://github.com/artragis) - cgabard (https://github.com/cgabard) +- coma94 (https://github.com/coma94) - Coy0te (https://github.com/Coy0te) - delphiki (https://github.com/delphiki) - Eskimon (https://github.com/Eskimon) - firm1 (https://github.com/firm1) +- Florian BOUX (https://github.com/Florianboux) - Ge0 (https://github.com/Ge0) - geoffreyc (https://github.com/geoffreyc) - GerardPaligot (https://github.com/GerardPaligot) @@ -20,3 +23,4 @@ Original code base (pdp / fork on 11-02-2013) : Romain Porte (MicroJoe) <microjo - Taluu (https://github.com/Taluu) - Thunderseb (https://github.com/Thunderseb) - vhf / victor felder (https://github.com/vhf) +- Vulser (https://github.com/Vulser) diff --git a/Gulpfile.js b/Gulpfile.js index 91193b187c..c03ec38d8d 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -1,7 +1,8 @@ var gulp = require("gulp"), $ = require("gulp-load-plugins")(), path = require("path"), - spritesmith = require("gulp.spritesmith"); + spritesmith = require("gulp.spritesmith"), + mainBowerFiles = require('main-bower-files'); var paths = { scripts: "assets/js/**", @@ -91,7 +92,7 @@ gulp.task("smileys", function() { }); gulp.task("vendors", function() { - return $.bowerFiles() + return gulp.src(mainBowerFiles()) .pipe($.newer("dist/js/vendors.js")) .pipe($.flatten()) // remove folder structure .pipe($.size({ title: "vendors", showFiles: true })) diff --git a/README.md b/README.md index 9504af2e1d..b8d7eb334f 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,9 @@ Zeste de Savoir =============== -Site internet communautaire codé à l'aide du framework Django 1.6. +Site internet communautaire codé à l'aide du framework [Django](https://www.djangoproject.com/) 1.6 et de [Python](https://www.djangoproject.com/) 2.7. * Lien du site : [zestedesavoir](http://www.zestedesavoir.com) -* Lien de teasing : [Teasing](http://zestedesavoir.com/teasing/) @@ -36,7 +35,7 @@ Fonctionnalités implementées Fonctionnalités à venir ----------------------- -Elles sont reportées essentiellement dans le [bugtraker](https://github.com/zestedesavoir/zds-site/issues) +Elles sont reportées essentiellement dans le [bugtraker](https://github.com/zestedesavoir/zds-site/issues). @@ -71,20 +70,20 @@ python manage.py loaddata fixtures/users.yaml fixtures/forums.yaml fixtures/topi Cela va créer plusieurs entitées : -* 3 utilisateurs (username/password) : +* 3 utilisateurs (utilisateur/mot de passe) : * user/user : Utilisateur normal * staff/staff : Utilisateur avec les droits d'un staff * admin/admin : Utilisateur avec les droits d'un staff et d'un admin -* 3 categories +* 3 catégories * 11 forums -* 3 topics with one answer -* 1 mp with 3 participants +* 3 sujets avec une réponse +* 1 message privé (MP) avec 3 participants * 3 catégories et 2 sous-catégories -### Conseil de developpement +### Conseils de developpement -Avant de faire une PR, vérifiez que votre code passe tous les tests unitaires et qu'il est compatible PEP-8 (sous peine de refus de Pull Request) en exécutant les commandes suivantes, pour le back : +Avant de faire une Pull Request (PR), vérifiez que votre code passe tous les tests unitaires et qu'il est compatible [PEP-8](http://legacy.python.org/dev/peps/pep-0008/) en exécutant les commandes suivantes, pour le back : ```console python manage.py test @@ -115,9 +114,11 @@ En savoir plus -------------- - [Comment déployer ZDS sur un serveur de production ?](doc/deploy.md) +- [Contribuer](CONTRIBUTING.md) +- [Comment contribuer : comprendre comment suivre le workflow (sur zds)](http://zestedesavoir.com/forums/sujet/324/comment-contribuer-comprendre-comment-suivre-le-workflow/) -Zeste de Savoir est basé sur un fork de [Progdupeu.pl](http://progdupeu.pl) ([Dépôt Bitbucket](https://bitbucket.org/MicroJoe/progdupeupl/)) \ No newline at end of file +Zeste de Savoir est basé sur un fork de [Progdupeu.pl](http://progdupeu.pl) ([Dépôt Bitbucket](https://bitbucket.org/MicroJoe/progdupeupl/)). diff --git a/assets/images/clem-home-quote.png b/assets/images/clem-home-quote.png new file mode 100644 index 0000000000..3e84b97706 Binary files /dev/null and b/assets/images/clem-home-quote.png differ diff --git a/assets/images/clem-home.png b/assets/images/clem-home.png new file mode 100644 index 0000000000..1ec7870d05 Binary files /dev/null and b/assets/images/clem-home.png differ diff --git a/assets/js/accessibility-links.js b/assets/js/accessibility-links.js index f9600be10a..9831e8c38d 100644 --- a/assets/js/accessibility-links.js +++ b/assets/js/accessibility-links.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Managment of accessibility links + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/accordeon.js b/assets/js/accordeon.js index 02a149dce9..9159c5f1d2 100644 --- a/assets/js/accordeon.js +++ b/assets/js/accordeon.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D - --------------------------------- Accordeon for sidebar + --------------------------------- + Author: Alex-D ========================================================================== */ (function($){ @@ -10,13 +10,13 @@ $(".main .sidebar.accordeon, .main .sidebar .accordeon").each(function(){ var $that = this; - $("h4 + ul", $that).each(function(){ + $("h4 + ul, h4 + ol", $that).each(function(){ if($(".current", $(this)).length === 0) $(this).hide(); }); $("h4", $that).click(function(e){ - $("+ ul", $(this)).slideToggle(100); + $("+ ul, + ol", $(this)).slideToggle(100); e.preventDefault(); e.stopPropagation(); diff --git a/assets/js/autocompletion.js b/assets/js/autocompletion.js index afeb2c9506..b5cd07715a 100644 --- a/assets/js/autocompletion.js +++ b/assets/js/autocompletion.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Sandhose / Quentin Gliech - --------------------------------- Add autocomplete for members names + --------------------------------- + Author: Sandhose / Quentin Gliech ========================================================================== */ (function($) { @@ -13,8 +13,8 @@ this.$dropdown = this.$wrapper.find(".autocomplete-dropdown"); this.$dropdown.css({ - top: "-" + this.$input.css("margin-bottom"), - left: this.$input.css("margin-left") + "marginTop": "-" + this.$input.css("margin-bottom"), + "left": this.$input.css("margin-left") }); this.$input.on("keyup", this.handleInput.bind(this)); @@ -59,6 +59,7 @@ case 13: // Enter e.preventDefault(); e.stopPropagation(); + this.enter(); break; } @@ -89,7 +90,7 @@ self.updateDropdown(self.sortList(data, search)); }) .fail(function(){ - console.log("something went wrong..."); + console.error("[Autocompletition] Something went wrong..."); }) ; this.updateDropdown(this.sortList(this.searchCache(search), search)); @@ -98,6 +99,8 @@ }, showDropdown: function(){ + if(this.$input.is("input")) + this.$dropdown.css("width", this.$input.outerWidth()); this.$dropdown.show(); }, @@ -256,10 +259,16 @@ function buildDom(input) { var $input = $(input), - $wrapper = $("<span>", { class: "autocomplete-wrapper" }), - $dropdown = $("<div>", { class: "autocomplete-dropdown" }); + $wrapper = $("<div/>", { + "class": "autocomplete-wrapper" + }), + $dropdown = $("<div/>", { + "class": "autocomplete-dropdown" + }) + ; return $input.addClass("autocomplete-input") + .attr("autocomplete", "off") .wrap($wrapper) .parent() .append($dropdown) diff --git a/assets/js/close-alert-box.js b/assets/js/close-alert-box.js index 7b03fa2c2f..278b8d66f0 100644 --- a/assets/js/close-alert-box.js +++ b/assets/js/close-alert-box.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Close alert-boxes + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/cookies-banner.js b/assets/js/cookies-banner.js index 924f088949..e91ef33b54 100644 --- a/assets/js/cookies-banner.js +++ b/assets/js/cookies-banner.js @@ -1,30 +1,47 @@ -(function(window, document, undefined) { - // Google Analytics ID - var gaProperty = "UA-27730868-1"; +/* ===== Zeste de Savoir ==================================================== + Manage tracking cookies message + --------------------------------- + Authors: Sandhose + Alex-D / Alexandre Demode + ========================================================================== */ +(function(document, undefined) { var $banner = $("#cookies-banner"); - // Disable tracking if the opt-out cookie exists. - var disableStr = "ga-disable-" + gaProperty; - if(document.cookie.indexOf("hasconsent=true") > -1){ - window[disableStr] = false; - } else if(document.cookie.indexOf("hasconsent=false") > -1){ - window[disableStr] = true; - } else { - $banner.show(); + function checkHasConsent(){ + if(document.cookie.indexOf("hasconsent=true") > -1){ + $("#gtm").after( + "<script>" + + "dataLayer = [{'gaTrackingId': 'UA-27730868-1'}];" + + "(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':" + + "new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0]," + + "j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=" + + "'//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);" + + "})(window,document,'script','dataLayer','GTM-WH7642');" + + "</script>" + ); + } else if(document.cookie.indexOf("hasconsent=false") === -1){ + // Accept for the next page + setHasConsent(true); + + // Show the banner + $banner.show(); + } } + checkHasConsent(); - function sethasconsent(hasconsent){ + + function setHasConsent(hasconsent){ document.cookie = "hasconsent="+hasconsent+"; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/"; - window[disableStr] = !hasconsent; - $banner.slideUp(200); } $("#reject-cookies").on("click", function(){ - sethasconsent(false); + setHasConsent(false); + $banner.slideUp(200); }); $("#accept-cookies").on("click", function(){ - sethasconsent(true); + checkHasConsent(); + $banner.slideUp(200); }); -})(window, document); \ No newline at end of file +})(document); \ No newline at end of file diff --git a/assets/js/data-click.js b/assets/js/data-click.js index df1899a933..e3ab6e31b2 100644 --- a/assets/js/data-click.js +++ b/assets/js/data-click.js @@ -1,13 +1,25 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Simulate click on element from another + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ "use strict"; - $("[data-click]").on("click focus", function(e){ + var dropdownMouseDown = false; + + $("[data-click]") + .on("mousedown", function(){ + dropdownMouseDown = true; + }) + .on("mouseup", function(){ + dropdownMouseDown = false; + }) + .on("click focus", function(e){ + if(e.type === "focus" && dropdownMouseDown) + return false; + if(!($(this).hasClass("dont-click-if-sidebar") && $(".header-container .mobile-menu-btn").is(":visible"))){ e.preventDefault(); e.stopPropagation(); diff --git a/assets/js/dropdown-menu.js b/assets/js/dropdown-menu.js index 5eb033ff6e..3c76b65778 100644 --- a/assets/js/dropdown-menu.js +++ b/assets/js/dropdown-menu.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Dropdown menu open/close + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ @@ -23,6 +23,9 @@ dropdownMouseDown = false; }) .on("click", function(e){ + if(($(this).parents(".header-menu-list").length > 0 && parseInt($("html").css("width")) < 960)) + return true; + e.preventDefault(); e.stopPropagation(); diff --git a/assets/js/find-solved-topics.js b/assets/js/find-solved-topics.js index d34e70dc41..c24d2702af 100644 --- a/assets/js/find-solved-topics.js +++ b/assets/js/find-solved-topics.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Search for solved topics when create a new topic + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/gallery.js b/assets/js/gallery.js index 8c20b03a57..25d1fa29c0 100644 --- a/assets/js/gallery.js +++ b/assets/js/gallery.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Sandhose / Quentin Gliech - --------------------------------- Gallery list and grid views management + --------------------------------- + Author: Sandhose / Quentin Gliech ========================================================================== */ (function($){ diff --git a/assets/js/karma-ajax.js b/assets/js/karma-ajax.js index 7c8ca47c8c..72748f1588 100644 --- a/assets/js/karma-ajax.js +++ b/assets/js/karma-ajax.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Manage karma AJAX requests (+1/-1 on messages) + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/keyboard-navigation.js b/assets/js/keyboard-navigation.js index 628548f3bf..0260126cb0 100644 --- a/assets/js/keyboard-navigation.js +++ b/assets/js/keyboard-navigation.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Keyboard navigation in navigables lists, with j/k keys + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/markdown-help.js b/assets/js/markdown-help.js index a0e4ba86d3..b4c99682fd 100644 --- a/assets/js/markdown-help.js +++ b/assets/js/markdown-help.js @@ -1,8 +1,8 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Ugly markdown help block management TEMP : Add this to the future awesome Markdown editor directly + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ @@ -14,7 +14,7 @@ "html": "<div class=\"markdown-help-more\">" + "<p>Les simples retours à la ligne ne sont pas pris en compte. Pour créer un nouveau paragraphe, pensez à <em>sauter une ligne</em> !</p>" + "<pre><code>**gras** \n*italique* \n[texte de lien](url du lien) \n> citation \n+ liste a puces </code></pre>" + - "<a href=\"http://zestedesavoir.com/articles/29/rediger-sur-zds/\">Voir la documentation complète</a></div>" + + "<a href=\"//zestedesavoir.com/tutoriels/221/rediger-sur-zds/\">Voir la documentation complète</a></div>" + "<a href=\"#open-markdown-help\" class=\"open-markdown-help btn btn-grey ico-after view\">"+ "<span class=\"close-markdown-help-text\">Masquer</span>" + "<span class=\"open-markdown-help-text\">Afficher</span> l'aide Markdown" + diff --git a/assets/js/message-hidden.js b/assets/js/message-hidden.js index a87c525576..722b902b34 100644 --- a/assets/js/message-hidden.js +++ b/assets/js/message-hidden.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Toggle message content for staff + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/mobile-menu.js b/assets/js/mobile-menu.js index f618da8191..79c492b2e0 100644 --- a/assets/js/mobile-menu.js +++ b/assets/js/mobile-menu.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Mobile sidebar menu, swipe open/close + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ @@ -178,11 +178,26 @@ if($elem.hasClass("mobile-show-ico")) $div.addClass("mobile-show-ico"); - var $links = ($elem.hasClass("mobile-all-links")) ? $("a:not(.action-hover)", $elem).addClass("mobile-menu-link") : $(".mobile-menu-link", $elem); + var $links = ($elem.hasClass("mobile-all-links")) ? $("a, button, span.disabled", $elem).not(".action-hover").addClass("mobile-menu-link") : $(".mobile-menu-link", $elem); $links.each(function(){ - if($(this).parents(".mobile-menu-imported").length === 0) - $div.append($(this).clone().addClass("light")); + if($(this).parents(".mobile-menu-imported, .modal").length === 0){ + var $elem = $(this).clone().addClass("light"); + var formId; + + if($(this).is("button")){ + var $form = $(this).parents("form:first"); + if(!$form.attr("id")){ + formId = "form" + $(".identified-form").length; + $form.attr("id", formId).addClass("identified-form"); + } else { + formId = $form.attr("id"); + } + $elem.attr("form", formId); + } + + $div.append($elem); + } }); $elem.addClass("mobile-menu-imported"); @@ -264,4 +279,35 @@ }, 200); } } + + + + + /** + * Manage actions buttons, move them at the top af core of page + */ + $(window).on("resize", function(){ + if(parseInt($("html").css("width")) < 960 && !disableMobileMenu){ + var $newBtns = $(".sidebar .new-btn:not(.mobile-btn-imported)"); + if($newBtns.length > 0){ + var $prevElem = $("#content > .content-wrapper, #content > .full-content-wrapper").find("h1, h2"); + if($prevElem.next(".license").length > 0) + $prevElem = $prevElem.next(".license"); + if($prevElem.next(".subtitle").length > 0) + $prevElem = $prevElem.next(".subtitle"); + if($prevElem.next(".taglist").length > 0) + $prevElem = $prevElem.next(".taglist"); + + var $newBtnContainer = $("<div/>", { + "class": "new-btn-container" + }); + $newBtns.each(function(){ + $newBtnContainer.append($(this).clone().removeAttr("id").removeClass("blue")); + $(this).addClass("mobile-btn-imported"); + }); + $prevElem.after($newBtnContainer); + } + } + }); + $(window).trigger("resize"); })(jQuery); \ No newline at end of file diff --git a/assets/js/modal.js b/assets/js/modal.js index 7b3bbcdcbe..2d1e348203 100644 --- a/assets/js/modal.js +++ b/assets/js/modal.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Manage modals boxes + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/select-autosubmit.js b/assets/js/select-autosubmit.js index 716cea1dc2..035f4e14f1 100644 --- a/assets/js/select-autosubmit.js +++ b/assets/js/select-autosubmit.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Auto submit forms + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/spoiler.js b/assets/js/spoiler.js index f8e29d4985..058cf32012 100644 --- a/assets/js/spoiler.js +++ b/assets/js/spoiler.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Toggle spoiler content + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/tab-modalize.js b/assets/js/tab-modalize.js index 739b8bdb01..8b42664dde 100644 --- a/assets/js/tab-modalize.js +++ b/assets/js/tab-modalize.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Keyboad accessibility for overlayed boxes (modals, etc) + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/js/zen-mode.js b/assets/js/zen-mode.js index 42aef19285..0f358892bf 100644 --- a/assets/js/zen-mode.js +++ b/assets/js/zen-mode.js @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Zen mode for content-pages + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ (function($){ diff --git a/assets/scss/_all-supports.scss b/assets/scss/_all-supports.scss index 16c28309e8..7f59097dee 100644 --- a/assets/scss/_all-supports.scss +++ b/assets/scss/_all-supports.scss @@ -1,12 +1,12 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Common style for all supports + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ - #accessibility { +#accessibility { list-style: none; margin: 0; padding: 0 2.5%; @@ -712,11 +712,13 @@ } } - h3 + ul { + h3 + ul, + h3 + ol { margin: 7px 0; } - ul { + ul, + ol { margin: 0; padding: 0; list-style: none; @@ -939,18 +941,21 @@ text-overflow: ellipsis; white-space: nowrap; - & + ul > li:first-child { - margin-top: 5px; + & + ul, + & + ol { + & > li:first-child { + margin-top: 5px; + } } } - ul li.current { + ol li.current { margin-top: 0 !important; padding-top: 5px; margin-bottom: 5px; background-color: #FFF; - ul { + ol { margin-top: 5px; padding-top: 5px; padding-bottom: 5px; @@ -1011,14 +1016,20 @@ height: 50px; } - &:not(:first-child) { + &:not(.subtitle):not(:first-child) { margin-top: 50px; } } + .license { + float: right; + margin-top: -45px; + } + .subtitle { font-size: 18px; font-size: 1.8rem; + line-height: 23px; color: #999; margin-top: -15px; margin-bottom: 15px; @@ -1027,6 +1038,12 @@ border-bottom: 1px solid #EEE; } + .pubdate { + display: block; + color: #999; + margin-bottom: 15px; + } + .member-item { margin-right: 7px; @@ -1043,19 +1060,162 @@ } .authors .member-item { margin-right: 0; + margin-left: 7px; + + .avatar { + height: 30px; + width: 30px; + margin: -3px 5px 0 -6px; + } } .open-zen-mode { display: none; } + + .new-btn-container { + display: none; + } } -.main.home .content-container { +.home .main .content-container { margin-top: 0; } + + /* ============== ALL: Main / Home ============== */ +.home-wrapper { + position: relative; + max-width: 1100px; + padding: 0 15px; + margin: 0 auto; +} +.home-quote { + width: 100%; + min-height: 24px; + background: #e9e7e4; + + blockquote { + color: #0b668e; + font-style: italic; + margin: 0; + width: 100%; + text-align: center; + } + + &.home-simple-quote { + padding: 20px 0; + + img { + position: absolute; + height: 80px; + top: -15px; + left: -20px; + } + + blockquote { + margin: 0; + font-size: 16px; + font-size: 1.6rem; + } + } + + &.home-full-description { + padding: 20px 0; + min-height: 210px; + text-align: center; + + img { + margin: 0 0 15px -100px; + } + + .home-full-description-text { + line-height: 18px; + text-align: left; + + h2 { + font-size: 22px; + font-size: 2.2rem; + line-height: 38px; + line-height: 3.8rem; + color: #084561; + font-weight: normal; + border-bottom: 1px solid #F8AD32; + margin-top: 0; + } + + blockquote { + color: #0b668e; + font-style: italic; + font-size: 18px; + font-size: 1.8rem; + margin: 0; + } + + p:last-child { + margin-bottom: 0; + } + } + } +} +.search-box { + width: 100%; + min-height: 60px; + background: #EEE; + box-shadow: rgba(0, 0, 0, .25) 0 0 3px; + + form { + min-height: 60px; + + label { + display: block; + line-height: 40px; + font-size: 16px; + font-size: 1.6rem; + color: #333; + text-align: center; + width: 100%; + } + + input, + button { + border: 1px solid $orange; + background: #FFF; + margin: 5px 0 12px; + } + input { + height: 34px; + padding: 0 15px; + border-right: none; + width: 85%; + width: calc(100% - 71px); + } + button { + height: 36px; + text-indent: -9999px; + border-left: none; + width: 40px; + transition: background .15s; + + position: absolute; + bottom: 0; + right: 15px; + + &:hover { + background: #EEE; + } + + &:after { + top: 9px; + left: 12px; + @include sprite-pos($search); + } + } + } +} + .tutorial-list article, .main .article-content .tutorial-list article { min-height: 60px; @@ -1135,6 +1295,10 @@ } .tutorial-infos { margin: 7px 0 0 70px; + + &.no-illu { + margin-left: 0; + } } } @@ -1281,7 +1445,7 @@ &.current { height: 38px; color: #808080; - background: #F4F6F6; + background: #F7F7F7; margin-top: -1px; border-left: 1px solid #d2d5d6; border-bottom: 3px solid #d2d5d6; @@ -1599,9 +1763,22 @@ .article-content { p, + > a, + p a, ul:not(.pagination), ol { - font-family: $font-serif; + font-family: $font-serif-active; + } + } + + .comment-author { + background: #EEE; + padding: 7px 15px; + + blockquote { + margin: 10px 0; + border-left: 5px solid #CCC; + padding: 5px 0 5px 15px; } } @@ -1811,6 +1988,9 @@ figure { margin: 15px 0; } + &:last-child { + margin-bottom: 15px; + } } code, @@ -1848,7 +2028,7 @@ } } - .reactions-title { + .comments-title { margin: 50px 0 20px; color: $blue; border-bottom: 1px solid $orange; @@ -2503,6 +2683,7 @@ form.topic-message { left: 0; right: 0; padding: 10px 20px 5px; + font-size: 15px; font-size: 1.5rem; line-height: 15px; color: #444; @@ -2557,15 +2738,27 @@ form.topic-message { ALL: Markdown help ============== */ .markdown-help { + min-height: 50px; + .open-markdown-help { - position: absolute; - bottom: 0; - left: 8px; - + float: none !important; + display: inline-block !important; + margin-bottom: 20px; + .close-markdown-help-text { display: none; } } + .topic-message & { + min-height: 0; + + .open-markdown-help { + position: absolute; + bottom: 0; + left: 8px; + margin-bottom: 0; + } + } .markdown-help-more { display: none; @@ -2639,9 +2832,16 @@ form.topic-message { } } - .version, - .version a { - color: rgba(255, 255, 255, .5); + .version { + &, + a { + color: rgba(255, 255, 255, .5); + } + + a:hover, + a:focus { + color: #FFF; + } } } @@ -2673,8 +2873,8 @@ form.topic-message { margin-bottom: 20px; background: $blue; color: #FFF; - font-size: 1.6rem; font-size: 16px; + font-size: 1.6rem; text-shadow: rgba(0, 0, 0, 0.75) 0 0 3px; &.ico-after { diff --git a/assets/scss/_base.scss b/assets/scss/_base.scss index 36281b2579..bc57bee44a 100644 --- a/assets/scss/_base.scss +++ b/assets/scss/_base.scss @@ -1,8 +1,8 @@ /* ===== Zeste de Savoir ==================================================== + Base style for globals elements & helpers + --------------------------------- Base from HTML5 BoilerPlate Updated by: Alex-D / Alexandre Demode - --------------------------------- - Base style for globals elements & helpers ========================================================================== */ diff --git a/assets/scss/_editor.scss b/assets/scss/_editor.scss index c6b7253d0b..ccc3887e03 100644 --- a/assets/scss/_editor.scss +++ b/assets/scss/_editor.scss @@ -6,7 +6,6 @@ margin: 0; padding: 2px; list-style-position: initial; - list-style-image: none; list-style-type: none; border-bottom: none; diff --git a/assets/scss/_extra-wide.scss b/assets/scss/_extra-wide.scss index 565c4dedbf..d38acd194b 100644 --- a/assets/scss/_extra-wide.scss +++ b/assets/scss/_extra-wide.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Only for extra-wide screens with width > 1140px + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ diff --git a/assets/scss/_form.scss b/assets/scss/_form.scss index c5a86d7af6..3edc8ebd92 100644 --- a/assets/scss/_form.scss +++ b/assets/scss/_form.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Form style + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ diff --git a/assets/scss/_high-pixel-ratio.scss b/assets/scss/_high-pixel-ratio.scss index b21e4ea376..b31b50c5c5 100644 --- a/assets/scss/_high-pixel-ratio.scss +++ b/assets/scss/_high-pixel-ratio.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Override for high pixel ratio screens : HD icons + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ @@ -13,11 +13,10 @@ } .ico, .ico-after:after, - .breadcrumb ul li:not(:last-child):after, + .breadcrumb ol li:not(:last-child):after, input[type=radio]:checked, input[type=checkbox]:checked { background-size: round($sprite_width / 2) round($sprite_height / 2) !important; - //background-size: 50% 50% !important; background-image: $sprite2x !important; } @@ -40,20 +39,26 @@ } } - .breadcrumb ul li:not(:last-child):after { + .breadcrumb ol li:not(:last-child):after { @include sprite-pos($ariane, true); } - .search form button:after { - @include sprite-pos($search, true); + .search, + .search-box { + form button:after { + @include sprite-pos($search, true); + } } - .main .content-container h2 { - &.ico-articles:after { - @include sprite-pos($articles, true); - } - &.ico-tutorials:after { - @include sprite-pos($tutorials, true); + .main .content-container { + h1, + h2 { + &.ico-articles:after { + @include sprite-pos($articles, true); + } + &.ico-tutorials:after { + @include sprite-pos($tutorials, true); + } } } diff --git a/assets/scss/_mega-wide.scss b/assets/scss/_mega-wide.scss index b929b8331d..4cbed9c0f4 100644 --- a/assets/scss/_mega-wide.scss +++ b/assets/scss/_mega-wide.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Only for mega-wide screens with width > 1360px + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ diff --git a/assets/scss/_mobile-tablet.scss b/assets/scss/_mobile-tablet.scss index e55049a615..0481e2b5ba 100644 --- a/assets/scss/_mobile-tablet.scss +++ b/assets/scss/_mobile-tablet.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Style only for mobiles and tablets + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ @@ -100,6 +100,10 @@ .mobile-menu-link { margin: 0; width: 100%; + + &.disabled { + opacity: .5; + } } &:not(.mobile-show-ico) .ico-after:after { @@ -134,6 +138,7 @@ .mobile-menu-link { display: block; height: 40px; + line-height: 40px; text-decoration: none; color: #CCC; font-size: 16px; @@ -141,6 +146,10 @@ text-overflow: ellipsis; white-space: nowrap; overflow: hidden; + background: none; + border: none; + text-align: left; + padding: 0; &.mobile-menu-sublink { width: 90%; @@ -381,9 +390,37 @@ } -/* ==================== - Content positionning - ==================== */ + .main .content-container .new-btn-container { + display: block; + margin: 30px 0; + border-top: 1px solid #DDD; + overflow: hidden; + + .new-btn { + display: block; + width: 100%; + padding: 7px 10px 7px 35px; + text-decoration: none; + height: 30px; + line-height: 30px; + background: #EEE; + color: #333; + border-bottom: 1px solid #DDD; + + &.ico-after:after { + top: 13px; + left: 10px; + } + } + } + + + + + + /* ==================== + Content positionning + ==================== */ .main { width: 100%; @@ -424,9 +461,11 @@ -/* ==================== - Forums - ==================== */ + + + /* ==================== + Forums + ==================== */ .topic-list { .topic { background: none !important; @@ -452,6 +491,7 @@ line-height: 20px; font-size: 12px; width: 50px; + margin-top: -2px; margin-left: 10px; &.push-badge { @@ -508,7 +548,7 @@ .message-karma { position: absolute; top: 35px; - left: 10px; + left: 7px; a, span { @@ -539,10 +579,18 @@ -/* ==================== - Tutorials/Articles - ==================== */ + + /* ==================== + Tutorials/Articles + ==================== */ + .main .content-container { + .taglist, + .pubdate { + margin-left: 10px; + margin-right: 10px; + } + .article-content { p, ul:not(.pagination) { @@ -551,10 +599,15 @@ font-size: 1.8ex; } } - .content-wrapper { + .content-wrapper, + .full-content-wrapper { h1, h2, - h3, + h3 { + padding-left: 10px; + padding-right: 10px; + } + h4, h5, h6, @@ -563,9 +616,10 @@ p, figure, blockquote { - margin-left: 15px; - margin-right: 15px; + margin-left: 10px; + margin-right: 10px; } + figure { p, blockquote { @@ -578,6 +632,12 @@ + + + /* ==================== + Footer + ==================== */ + .page-footer { text-align: center; height: auto; diff --git a/assets/scss/_mobile.scss b/assets/scss/_mobile.scss index 65429ced7e..4fe69521e9 100644 --- a/assets/scss/_mobile.scss +++ b/assets/scss/_mobile.scss @@ -1,14 +1,13 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Style only for mobiles and tablets + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ @media only screen and (max-width: 760px) { #cookies-banner { - display: block; position: absolute; top: 50px; right: 0; diff --git a/assets/scss/_print.scss b/assets/scss/_print.scss index 754ea075c3..75bcbe979c 100644 --- a/assets/scss/_print.scss +++ b/assets/scss/_print.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - From HTML5 BoilerPlate - ---------------------- Print styles + ---------------------- + From HTML5 BoilerPlate ========================================================================== */ diff --git a/assets/scss/_pygments.scss b/assets/scss/_pygments.scss index ad4538a228..9ba78812c4 100644 --- a/assets/scss/_pygments.scss +++ b/assets/scss/_pygments.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Updated by: Alex-D / Alexandre Demode - --------------------------------- Pygments style + --------------------------------- + Updated by: Alex-D / Alexandre Demode ========================================================================== */ .codehilite .hll { background-color: #ffffcc } diff --git a/assets/scss/_tablet.scss b/assets/scss/_tablet.scss index f5ea0d1bb2..2252d08e7b 100644 --- a/assets/scss/_tablet.scss +++ b/assets/scss/_tablet.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Tablet and more + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ diff --git a/assets/scss/_wide.scss b/assets/scss/_wide.scss index 3e1e9421fc..7fa0db22a9 100644 --- a/assets/scss/_wide.scss +++ b/assets/scss/_wide.scss @@ -1,7 +1,7 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- Wide screen support, for wide and extra-wide + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ @@ -127,7 +127,7 @@ background-image:linear-gradient(left, rgba(231, 235, 236, 0), rgba(231, 235, 236, .75)); } - ul { + ol { margin: 0; padding: 0; list-style: none; @@ -227,6 +227,10 @@ width: 80%; margin-right: 0; + .taglist + .pubdate { + margin-top: -40px; + } + .open-zen-mode { display: block; } @@ -247,6 +251,7 @@ } .reactions-title, + .pagination:not(.pagination-chapter), .topic-message { display: none; } @@ -281,7 +286,8 @@ h3, h4, - ul li { + ul li, + ol li { padding-left: 11.5%; } @@ -298,7 +304,8 @@ } &.sommaire ul li.current { - ul { + ul, + ol { margin-left: calc(-11% - 10px); width: calc(111% + 10px); background: linear-gradient(top, rgba(0, 0, 0, .07), transparent 3px); @@ -315,6 +322,54 @@ /* ============ Main / Home ============ */ + .home-quote { + blockquote { + text-align: left; + } + + &.home-simple-quote { + img { + height: 80px; + top: -15px; + left: 150px; + margin-left: -10px; + } + + blockquote { + margin: 0 0 0 300px; + } + } + + &.home-full-description { + img { + float: left; + margin: 15px 0 0 -15px; + } + + .home-full-description-text { + margin-left: 300px; + } + } + } + .search-box { + form { + label { + line-height: 60px; + width: 300px; + float: left; + } + + input, + button { + margin: 12px 0; + float: left; + } + input { + width: calc(100% - 371px); + } + } + } + .content-cols .main { .content-container { width: 79%; @@ -343,7 +398,11 @@ Main / Message ============ */ .topic-message { - margin: 0 0 25px; + margin: 25px 0; + + &:first-child { + margin-top: 35px; + } .user, .message { diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 60a345b616..04a04e12eb 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -1,14 +1,11 @@ /* ===== Zeste de Savoir ==================================================== - Author: Alex-D / Alexandre Demode - --------------------------------- All styles for *Zeste de Savoir* website + --------------------------------- + Author: Alex-D / Alexandre Demode ========================================================================== */ -//@import "compass"; -//@import "compass/css3/user-interface"; - /** @@ -49,21 +46,14 @@ $font-serif-active: "Merriweather", $font-serif; $font-monospace-active: "Source Code Pro", $font-monospace; -/** - * Custom mixins - */ -@mixin sprite-pos($_item, $retina: false){ - @if($retina == false){ - background-position: nth($_item, 1) nth($_item, 2); - } @else { - background-position: round(nth($_item, 1)/ 2) round(nth($_item, 2)/ 2); - } -} + + /** * Import external mixins */ @import "mixins/display-flex"; +@import "mixins/sprite-pos"; /** * Import sprite diff --git a/assets/scss/mixins/_sprite-pos.scss b/assets/scss/mixins/_sprite-pos.scss new file mode 100644 index 0000000000..2327d2c766 --- /dev/null +++ b/assets/scss/mixins/_sprite-pos.scss @@ -0,0 +1,13 @@ +/* ========================================================================== + Mixin for sprite position auto-calculated + --------------------------------- + Author: Alex-D / Alexandre Demode + ========================================================================== */ + +@mixin sprite-pos($_item, $retina: false){ + @if($retina == false){ + background-position: nth($_item, 1) nth($_item, 2); + } @else { + background-position: round(nth($_item, 1)/ 2) round(nth($_item, 2)/ 2); + } +} \ No newline at end of file diff --git a/fixtures/image_test.jpg b/fixtures/image_test.jpg new file mode 100644 index 0000000000..ab67be56da Binary files /dev/null and b/fixtures/image_test.jpg differ diff --git a/package.json b/package.json index 01c263ce31..bc1c02e576 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "dependencies": { "gulp": "^3.7.0", "gulp-autoprefixer": "0.0.7", - "gulp-bower-files": "^0.2.4", "gulp-cache": "^0.1.11", "gulp-clean": "^0.3.0", "gulp-compass": "^1.1.9", @@ -42,6 +41,7 @@ "gulp-uglify": "^0.3.0", "gulp-zip": "^0.3.4", "gulp.spritesmith": "^1.1.0", - "jshint-stylish": "^0.2.0" + "jshint-stylish": "^0.2.0", + "main-bower-files": "~1.0.0" } } diff --git a/requirements.txt b/requirements.txt index b5e5adc8b3..b653a5df66 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,8 +28,8 @@ factory-boy==2.2.1 django-discover-runner==0.4 pygeoip==0.3.1 pillow -git+https://github.com/zestedesavoir/Python-ZMarkdown.git@2.4.1-zds.5 -git+https://github.com/zestedesavoir/GitPython.git@0.3.2-RC1.z2 +https://github.com/zestedesavoir/GitPython/archive/0.3.2-RC1.z2.zip +https://github.com/zestedesavoir/Python-ZMarkdown/archive/2.4.1-zds.9.zip flake8 autopep8 -easy-thumbnails \ No newline at end of file +easy-thumbnails diff --git a/robots.txt b/robots.txt index 18d2cd5258..d24009198d 100644 --- a/robots.txt +++ b/robots.txt @@ -1,8 +1,6 @@ -# www.robotstxt.org/ -# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 +# www.robotstxt.org Sitemap: http://zestedesavoir.com/sitemap.xml User-agent: * -Disallow: /membres/ Disallow: /mp/ \ No newline at end of file diff --git a/templates/500.html b/templates/500.html index 0efed39cb0..e0a6a3c115 100644 --- a/templates/500.html +++ b/templates/500.html @@ -9,7 +9,7 @@ {% block headline %} - <h2 class="box">Erreur 500</h2> + Erreur 500 {% endblock %} diff --git a/templates/article/base.html b/templates/article/base.html index 7fb5f99cb4..8bc25a9713 100644 --- a/templates/article/base.html +++ b/templates/article/base.html @@ -3,7 +3,7 @@ {% block title_base %} - · Articles + • Articles {% endblock %} @@ -15,7 +15,11 @@ {% block breadcrumb_base %} - <li><a href="{% url "zds.article.views.index" %}">Articles</a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{% url "zds.article.views.index" %}" itemprop="url"> + <span itemprop="title">Articles</span> + </a> + </li> {% endblock %} @@ -28,6 +32,6 @@ {% block sidebar %} <aside class="sidebar mobile-menu-hide"> - {% include "article/includes/sidebar_base.html" %} - </aside> + {% include "article/includes/sidebar_base.html" %} + </aside> {% endblock %} \ No newline at end of file diff --git a/templates/article/base_content.html b/templates/article/base_content.html index e289d13885..26d5909324 100644 --- a/templates/article/base_content.html +++ b/templates/article/base_content.html @@ -3,7 +3,7 @@ {% block title_base %} - · Articles + • Articles {% endblock %} @@ -15,7 +15,11 @@ {% block breadcrumb_base %} - <li><a href="{% url "zds.article.views.index" %}">Articles</a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{% url "zds.article.views.index" %}" itemprop="url"> + <span itemprop="title">Articles</span> + </a> + </li> {% endblock %} diff --git a/templates/article/find.html b/templates/article/find.html index 941af27ff9..e31e2953b8 100644 --- a/templates/article/find.html +++ b/templates/article/find.html @@ -27,7 +27,7 @@ {% endwith %} <li> - <a href="{% url "zds.article.views.find_article" usr.username %}"> + <a href="{% url "zds.article.views.find_article" usr.pk %}"> Articles rédigés </a> </li> diff --git a/templates/article/includes/article_item.part.html b/templates/article/includes/article_item.part.html index 52e8afd4c8..e321acc93f 100644 --- a/templates/article/includes/article_item.part.html +++ b/templates/article/includes/article_item.part.html @@ -15,11 +15,17 @@ <img src="{{ article.image.article_illu.url }}" alt="" class="tutorial-img avatar"> {% endif %} - <div class="tutorial-infos"> - <h3>{{ article.title }}</h3> + <div class="tutorial-infos {% if not article.image %}no-illu{% endif %}"> + <h3 itemprop="itemListElement">{{ article.title }}</h3> <p class="article-metadata"> - Publié le {{ article.pubdate|format_date }} - - <a href="{{ article.get_last_reaction.get_absolute_url }}"> + {{ article.pubdate|format_date|capfirst }} - + <a href=" + {% if article.get_reaction_count == 0 %} + {{ link }}#reactions + {% else %} + {{ article.get_last_reaction.get_absolute_url }} + {% endif %} + "> {% if article.get_reaction_count == 0 %} Aucune réaction {% elif article.get_reaction_count == 1 %} diff --git a/templates/article/includes/sidebar_actions.part.html b/templates/article/includes/sidebar_actions.part.html index 514897a486..0774394e1f 100644 --- a/templates/article/includes/sidebar_actions.part.html +++ b/templates/article/includes/sidebar_actions.part.html @@ -74,45 +74,13 @@ <h3>Gestion</h3> </li> {% else %} <li class="inactive"> - <span>En attente de validation</span> - </li> - {% endif %} - - {% if article.sha_public = None %} - <li> - <a href="#delete" class="open-modal ico-after cross red">Supprimer</a> - <form action="{% url "zds.article.views.modify" %}" method="post" class="modal modal-small" id="delete"> - <p> - Vous vous apprêtez à supprimer <strong>définitivement</strong> l'article "<em>{{ article.title }}</em>". - </p> - - <input type="hidden" name="article" value="{{ article.pk }}"> - <input type="hidden" name="version" value="{{ version }}"> - {% csrf_token %} - <button type="submit" name="delete"> - Confirmer - </button> - </form> - </li> - <li> + <span class="ico-after tick">En attente de validation</span> </li> {% endif %} </ul> </div> {% endif %} -<div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Télécharger"> - <h3>Télécharger</h3> - <ul> - <li> - <a href="{% url "zds.article.views.download" %}?article={{ article.pk }}" class="ico-after download blue"> - Archive - </a> - </li> - </ul> -</div> - - {% if perms.article.change_article %} @@ -128,10 +96,23 @@ <h3>Validation</h3> {% if not article.sha_validation = None %} {% if validation.is_pending %} <li> - <a href="{% url "zds.article.views.reservation" validation.pk %}" class="ico-after lock blue">Réserver</a> + <form action="{% url "zds.article.views.reservation" validation.pk %}" method="post"> + {% csrf_token %} + <button type="submit" class="ico-after lock blue"> + Réserver + </button> + </form> </li> {% elif validation.is_pending_valid %} {% if validation.validator = user %} + <li> + <form action="{% url "zds.article.views.reservation" validation.pk %}" method="post"> + {% csrf_token %} + <button type="submit" class="ico-after lock blue"> + Annuler la réservation + </button> + </form> + </li> <li> <a href="#publish" class="open-modal ico-after tick green">Publier</a> <form action="{% url "zds.article.views.modify" %}" method="post" class="modal modal-medium" id="publish"> @@ -156,9 +137,18 @@ <h3>Validation</h3> </li> {% else %} <li> - <a href="{% url "zds.article.views.reservation" validation.pk %}" class="open-modal ico-after cross blue"> - Réservé par {{ validation.validator.username }}, le retirer + <a href="#unreservation" class="open-modal ico-after lock blue"> + Réservé par <strong>{{ validation.validator.username }}</strong>, le retirer </a> + <form action="{% url "zds.article.views.reservation" validation.pk %}" method="post" class="modal modal-small" id="unreservation"> + {% csrf_token %} + <p> + La validation de cet article est actuellement réservé par {% include "misc/member_item.part.html" with member=validation.validator %}. Êtes-vous certains de vouloir le retirer ? + </p> + <button type="submit"> + Confirmer + </button> + </form> </li> {% endif %} {% endif %} @@ -182,4 +172,48 @@ <h3>Validation</h3> {% endif %} </ul> </div> -{% endif %} \ No newline at end of file +{% endif %} + + + +{% if user in authors.all or perms.article.change_article %} + <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Supprimer"> + <h3>Supprimer</h3> + <ul> + {% if article.sha_public = None %} + <li> + <a href="#delete" class="open-modal ico-after cross red">Supprimer</a> + <form action="{% url "zds.article.views.modify" %}" method="post" class="modal modal-small" id="delete"> + <p> + Vous vous apprêtez à supprimer <strong>définitivement</strong> l'article "<em>{{ article.title }}</em>". + </p> + + <input type="hidden" name="article" value="{{ article.pk }}"> + <input type="hidden" name="version" value="{{ version }}"> + {% csrf_token %} + <button type="submit" name="delete"> + Confirmer + </button> + </form> + </li> + {% else %} + <li class="inactive"> + <span class="disabled">Impossible, car en publié</span> + </li> + {% endif %} + </ul> + </div> +{% endif %} + + + +<div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Télécharger"> + <h3>Télécharger</h3> + <ul> + <li> + <a href="{% url "zds.article.views.download" %}?article={{ article.pk }}" class="ico-after download blue"> + Archive + </a> + </li> + </ul> +</div> \ No newline at end of file diff --git a/templates/article/includes/sidebar_base.html b/templates/article/includes/sidebar_base.html index 8930702010..b4d3f85588 100644 --- a/templates/article/includes/sidebar_base.html +++ b/templates/article/includes/sidebar_base.html @@ -6,21 +6,6 @@ Nouvel article </a> -{% if user.is_authenticated %} - {% captureas sidebarfilter %}{% spaceless %} - {% block sidebar_filter %}{% endblock %} - {% endspaceless %}{% endcaptureas %} - - {% if sidebarfilter %} - <div class="mobile-menu-bloc mobile-all-links" data-title="Filtres"> - <h3>Filtres</h3> - <ul> - {{ sidebarfilter|safe }} - </ul> - </div> - {% endif %} -{% endif %} - <div class="mobile-menu-bloc mobile-all-links" data-title="Flux"> <h3>Flux</h3> <ul> diff --git a/templates/article/index.html b/templates/article/index.html index a03a17e485..6d0c8c392f 100644 --- a/templates/article/index.html +++ b/templates/article/index.html @@ -3,34 +3,51 @@ {% block title %} - Liste des articles + {% if tag %} + {{ tag }} + {% else %} + Tous les articles + {% endif %} {% endblock %} -{% block meta %} - <meta name="description" content="Les articles sont rédigés par la - communauté. Ils permettent aussi bien de se tenir informé des dernières - innovations du moment que de découvrir de nouvelles notions liées aux - différents domaines de l'informatique."> +{% block description %} + {% if tag %} + Articles de la communauté à propos de {{ tag }}. Vous pourrez également découvrir de nouvelles notions en parcourant les autres catégories. + {% else %} + Les articles sont rédigés par la communauté. Ils permettent aussi bien de se tenir + informé des dernières innovations du moment que de découvrir de nouvelles notions + liées à différents domaines : sciences, informatiques, ... + {% endif %} {% endblock %} {% block breadcrumb %} - <li>Liste des articles</li> + {% if tag %} + <li>{{ tag }}</li> + {% else %} + <li>Tous les articles</li> + {% endif %} {% endblock %} {% block content_out %} - <section class="full-content-wrapper"> - <h2 class="ico-after ico-articles"> + <section class="full-content-wrapper" itemscope itemtype="http://schema.org/ItemList"> + <h2 class="ico-after ico-articles" itemprop="name"> {% block headline %} - Liste des articles + {% if tag %} + Articles : {{ tag }} + {% else %} + Tous les articles + {% endif %} {% endblock %} </h2> + <meta itemprop="itemListOrder" content="Unordered"> + {% block content %} {% if articles %} <div class="tutorial-list"> diff --git a/templates/article/member/edit.html b/templates/article/member/edit.html index 3bb3dc8dad..f98ed703be 100644 --- a/templates/article/member/edit.html +++ b/templates/article/member/edit.html @@ -5,13 +5,17 @@ {% block title_base %} - · Mes articles + • Articles {% endblock %} {% block breadcrumb_base %} - <li><a href="{% url "zds.member.views.articles" %}">Mes articles</a></li> + {% if user in article.authors.all %} + <li><a href="{% url "zds.member.views.articles" %}">Mes articles</a></li> + {% else %} + <li><a href="{% url "zds.article.views.index" %}">Articles</a></li> + {% endif %} {% endblock %} @@ -30,7 +34,7 @@ {% block headline %} - <h2>Éditer l'article : {{ article.title }}</h2> + <h1>Éditer l'article : {{ article.title }}</h1> {% endblock %} diff --git a/templates/article/member/history.html b/templates/article/member/history.html index e9039cfe84..cc368a5290 100644 --- a/templates/article/member/history.html +++ b/templates/article/member/history.html @@ -1,12 +1,22 @@ {% extends "article/base_content.html" %} {% load emarkdown %} -{% load date %} {% load profile %} +{% load date %} {% load thumbnail %} {% block title %} - {{ article.title }} + Historique de "{{ article.title }}" +{% endblock %} + + + +{% block breadcrumb_base %} + {% if user in article.authors.all %} + <li><a href="{% url "zds.member.views.articles" %}">Mes articles</a></li> + {% else %} + <li><a href="{% url "zds.article.views.index" %}">Articles</a></li> + {% endif %} {% endblock %} @@ -29,11 +39,40 @@ <h1 {% if article.image %}class="illu"{% endif %}> {% if article.image %} <img src="{{article.image.article_illu.url }}" alt=""> {% endif %} - Historique de l'article "{{ article.title }}" + Historique de "{{ article.title }}" </h1> + {% if article.licence %} + <span class="license" itemprop="license"> + {{ article.licence }} + </span> + {% endif %} + + {% if article.description %} + <h2 class="subtitle"> + {{ article.description }} + </h2> + {% endif %} + + <ul class="taglist" itemprop="keywords"> + {% for tag in tags.all %} + <li> + <a href="{% url "zds.article.views.index" %}?tag={{ tag.title }}"> + {{ tag.title }} + </a> + </li> + {% endfor %} + </ul> + + <span class="pubdate"> + Publié + <time datetime="{{ article.pubdate|date:"c" }}" pubdate="pubdate" itemprop="datePublished"> + {{ article.pubdate|format_date }} + </time> + </span> + <div class="authors"> - <span class="authors-label">Contributeurs : </span> + <span class="authors-label">Contributeur{{ article.authors.all|pluralize }} : </span> <ul> {% for member in article.authors.all %} <li> @@ -47,55 +86,55 @@ <h1 {% if article.image %}class="illu"{% endif %}> {% block content %} -<table> - <thead> - <tr> - <th>Visbilité</th> - <th width="25%">Date</th> - <th>Version</th> - <th width="10%">Diff</th> - <th width="10%">Auteur</th> - </tr> - </thead> - <tbody> - {% for log in logs %} + <table class="fullwidth"> + <thead> <tr> - <td> - {% if article.sha_public = log.newhexsha %} - Publique - {% elif article.sha_validation = log.newhexsha %} - Validation - {% elif article.sha_beta = log.newhexsha %} - Beta - {% elif article.sha_draft = log.newhexsha %} - Brouillon - {% endif %} - </td> - <td> - {{ log.time.0|format_date:True|capfirst }} - </td> - <td> - <a href="{% url "zds.article.views.view" article.pk article.slug %}?version={{ log.newhexsha }}"> - {{ log.message }} - </a> - </td> - <td> - {# TODO : proposer les diffs sur les articles #} - {# <a href="{% url "zds.article.views.diff" article.pk article.slug %}?sha={{ log.newhexsha }}" > #} - {{ log.newhexsha|truncatechars:8 }} - {# </a> #} - </td> - <td> - {% with u=log.actor.name|user %} - {% if u %} - {% include "misc/member_item.part.html" with member=u %} - {% else %} - Inconnu - {% endif %} - {% endwith %} - </td> + <th>Visbilité</th> + <th width="25%">Date</th> + <th>Version</th> + <th width="10%">Diff</th> + <th width="10%">Auteur</th> </tr> - {% endfor %} - </tbody> -</table> -{% endblock %} \ No newline at end of file + </thead> + <tbody> + {% for log in logs %} + <tr> + <td> + {% if article.sha_public = log.newhexsha %} + Publique + {% elif article.sha_validation = log.newhexsha %} + Validation + {% elif article.sha_beta = log.newhexsha %} + Beta + {% elif article.sha_draft = log.newhexsha %} + Brouillon + {% endif %} + </td> + <td> + {{ log.time.0|humane_time }} + </td> + <td> + <a href="{% url "zds.article.views.view" article.pk article.slug %}?version={{ log.newhexsha }}"> + {{ log.message }} + </a> + </td> + <td> + {# TODO : proposer les diffs sur les articles #} + {# <a href="{% url "zds.article.views.diff" article.pk article.slug %}?sha={{ log.newhexsha }}" > #} + {{ log.newhexsha|truncatechars:8 }} + {# </a> #} + </td> + <td> + {% with u=log.actor.name|user %} + {% if u %} + {% include "misc/member_item.part.html" with member=u avatar=True %} + {% else %} + <em>Inconnu</em> + {% endif %} + {% endwith %} + </td> + </tr> + {% endfor %} + </tbody> + </table> +{% endblock %} diff --git a/templates/article/member/index.html b/templates/article/member/index.html index 1133350d8c..7703d51a02 100644 --- a/templates/article/member/index.html +++ b/templates/article/member/index.html @@ -3,7 +3,7 @@ {% block title_base %} - · Mes articles + • Articles {% endblock %} @@ -14,22 +14,69 @@ +{% block breadcrumb %} + {% if request.GET.type == "public" %} + <li>Publiés</li> + {% elif request.GET.type == "draft" %} + <li>Brouillons</li> + {% else %} + <li>Tous mes articles</li> + {% endif %} +{% endblock %} + + + {% block title %} Mes Articles + {% if request.GET.type == "public" %} + / Publiés + {% elif request.GET.type == "draft" %} + / Brouillons + {% endif %} {% endblock %} {% block headline %} Mes Articles + {% if request.GET.type == "public" %} + / Publiés + {% elif request.GET.type == "draft" %} + / Brouillons + {% endif %} {% endblock %} -{% block sidebar_filter %} - <li><a href="{% url "zds.member.views.articles" %}">Tous</a></li> - <li><a href="{% url "zds.member.views.articles" %}?type=public">Publiés</a></li> - <li><a href="{% url "zds.member.views.articles" %}?type=draft">Brouillons</a><li> +{% block sidebar %} + <aside class="sidebar mobile-menu-hide"> + <a href="{% url "zds.article.views.new" %}" class="ico-after more blue new-btn"> + Nouvel article + </a> + + <div class="mobile-menu-bloc mobile-all-links" data-title="Filtres"> + <h3>Filtres</h3> + <ul> + <li> + <a href="{% url "zds.member.views.articles" %}?type=public" class="ico-after tick green {% if request.GET.type == "public" %}unread{% endif %}"> + Publiés + </a> + </li> + <li> + <a href="{% url "zds.member.views.articles" %}?type=draft" class="ico-after tick {% if request.GET.type == "draft" %}unread{% endif %}"> + Brouillons + </a> + </li> + {% if request.GET.type %} + <li> + <a href="{% url "zds.member.views.articles" %}" class="ico-after cross"> + Annuler le filtre + </a> + </li> + {% endif %} + </ul> + </div> + </aside> {% endblock %} diff --git a/templates/article/member/new.html b/templates/article/member/new.html index f81cc5c606..5157bbd86e 100644 --- a/templates/article/member/new.html +++ b/templates/article/member/new.html @@ -5,7 +5,7 @@ {% block title_base %} - · Mes articles + • Articles {% endblock %} @@ -29,7 +29,7 @@ {% block headline %} - <h2>Nouvel article</h2> + <h1>Nouvel article</h1> {% endblock %} diff --git a/templates/article/member/view.html b/templates/article/member/view.html index 4b857cedaa..fa97abeaae 100644 --- a/templates/article/member/view.html +++ b/templates/article/member/view.html @@ -1,18 +1,23 @@ {% extends "article/base_content.html" %} {% load emarkdown %} {% load profile %} +{% load date %} {% load thumbnail %} {% block title_base %} - · Mes articles + • Articles {% endblock %} {% block breadcrumb_base %} - <li><a href="{% url "zds.member.views.articles" %}">Mes articles</a></li> + {% if user in authors.all %} + <li><a href="{% url "zds.member.views.articles" %}">Mes articles</a></li> + {% else %} + <li><a href="{% url "zds.article.views.index" %}">Articles</a></li> + {% endif %} {% endblock %} @@ -42,6 +47,18 @@ <h1 {% if article.image %}class="illu"{% endif %}> {% endif %} {{ article.title }} </h1> + + {% if article.licence %} + <span class="license"> + Licence {{ article.licence }} + </span> + {% endif %} + + {% if article.description %} + <h2 class="subtitle"> + {{ article.description }} + </h2> + {% endif %} <ul class="taglist"> {% for tag in tags.all %} @@ -52,9 +69,18 @@ <h1 {% if article.image %}class="illu"{% endif %}> </li> {% endfor %} </ul> + + {% if article.sha_public %} + <span class="pubdate"> + Publié + <time datetime="{{ article.pubdate|date:"c" }}" pubdate="pubdate" itemprop="datePublished"> + {{ article.pubdate|format_date }} + </time> + </span> + {% endif %} <div class="authors"> - <span class="authors-label">Contributeurs : </span> + <span class="authors-label">Contributeur{{ authors.all|pluralize }} : </span> <ul> {% for member in authors.all %} <li> @@ -68,17 +94,29 @@ <h1 {% if article.image %}class="illu"{% endif %}> </li> </ul> </div> + + {% if perms.article.change_article and article.sha_validation != None %} + <p class="content-wrapper alert-box info"> + Cet article est en attente de validation + </p> + {% if validation.comment_authors %} + <div class="content-wrapper comment-author"> + <p> + Le message suivant a été laissé à destination des validateurs : + </p> + + <blockquote> + {{ validation.comment_authors }} + </blockquote> + </div> + {% endif %} + {% endif %} {% endblock %} {% block content %} - <h3>Description</h3> - <p> - {{ article.description }} - </p> - <hr> <div> {{ article.txt|emarkdown }} </div> -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/article/validation/history.html b/templates/article/validation/history.html index b34f30a717..591b823d7d 100644 --- a/templates/article/validation/history.html +++ b/templates/article/validation/history.html @@ -1,5 +1,6 @@ {% extends "article/base.html" %} {% load date %} +{% load captureas %} @@ -18,80 +19,58 @@ {% block headline %} - <h2>Historique de validation de : {{ article.title }}</h2> + {{ article.title }} {% endblock %} -{% block sidebar_actions %} - {% include 'article/includes/sidebar_actions.part.html' %} +{% block headline_sub %} + Historique de validation {% endblock %} {% block content %} + <ul class="taglist"> + {% for tag in tags.all %} + <li> + <a href="{% url "zds.article.views.index" %}?tag={{ tag.title }}"> + {{ tag.title }} + </a> + </li> + {% endfor %} + </ul> + + <div class="authors"> + <span class="authors-label">Contributeur{{ authors.all|pluralize }} : </span> + <ul> + {% for member in authors.all %} + <li> + {% include "misc/member_item.part.html" with avatar=True %} + </li> + {% endfor %} + </ul> + </div> + {% if validations %} - <table> + <table class="fullwidth"> <thead> <tr> - <th>Titre</th> - <th width="8%">Catégorie(s)</th> - <th width="8%">Auteur(s)</th> - <th width="8%">Proposé</th> - <th width="8%">Validateur</th> - <th width="8%">Réservé</th> - <th width="8%">Action</th> + <th width="50%">Proposé</th> + <th width="50%">Statut</th> </tr> </thead> <tbody> {% for validation in validations %} <tr> <td> - <a href="{% url "zds.article.views.view" validation.article.pk validation.article.slug %}?version={{ validation.version }}" >{{ validation.article.title }}</a> - </td> - <td> - {% for subcategory in validation.article.subcategory.all %} - <a href="{% url "zds.article.views.history_validation" article.pk %}?subcategory={{subcategory.pk}}"> - {{ subcategory.title }} - </a> - {% endfor %} - </td> - <td> - {% for author in validation.article.authors.all %} - <a href="{% url "zds.member.views.details" user.username %}"> - {{ author.username }} - </a> - {% endfor %} - </td> - <td> - <span>{{ validation.date_proposition|format_date|capfirst }}</span> - </td> - <td> - {% if validation.validator %} - <a href="{% url "zds.member.views.details" validation.validator.username %}"> - {{ validation.validator.username }} - </a> - {% endif %} - </td> - <td> - {% if validation.date_reserve %} - <span>{{ validation.date_reserve|format_date|capfirst }}</span> - {% endif %} + {{ validation.date_proposition|format_date|capfirst }} </td> <td> - {% if validation.is_pending %} - <a href="{% url "zds.article.views.reservation" validation.pk %}"> - Réservé - </a> - {% elif validation.is_pending_valid %} - <a href="{% url "zds.article.views.reservation" validation.pk %}"> - Annuler la réservation - </a> - {% elif validation.is_accept %} - <span>Accepté</span> - {% elif validation.is_reject %} - <span>Rejeté</span> - {% endif %} + {% captureas reservation_url %} + {% url "zds.article.views.reservation" validation.pk %} + {% endcaptureas %} + {% include "misc/validation.part.html" %} </td> </tr> {% endfor %} diff --git a/templates/article/validation/index.html b/templates/article/validation/index.html index a93b449266..7d509cfc65 100644 --- a/templates/article/validation/index.html +++ b/templates/article/validation/index.html @@ -22,20 +22,22 @@ <h3>Filtres</h3> <ul> <li> - <a href="{% url "zds.article.views.list_validation" %}?type=reserved"> + <a href="{% url "zds.tutorial.views.list_validation" %}?type=reserved" class="ico-after tick green {% if request.GET.type == "reserved" %}unread{% endif %}"> En cours de validation </a> </li> <li> - <a href="{% url "zds.article.views.list_validation" %}?type=orphan"> + <a href="{% url "zds.tutorial.views.list_validation" %}?type=orphan" class="ico-after tick {% if request.GET.type == "orphan" %}unread{% endif %}"> En attente de validateur </a> </li> - <li> - <a href="{% url "zds.article.views.list_validation" %}"> - Annuler le filtre - </a> - </li> + {% if request.GET.type %} + <li> + <a href="{% url "zds.tutorial.views.list_validation" %}" class="ico-after cross blue"> + Annuler le filtre + </a> + </li> + {% endif %} </ul> </div> {% endblock %} diff --git a/templates/article/view.html b/templates/article/view.html index 4657b1a504..13de5eb1b0 100644 --- a/templates/article/view.html +++ b/templates/article/view.html @@ -1,6 +1,7 @@ {% extends "article/base_content.html" %} {% load emarkdown %} {% load captureas %} +{% load date %} {% load profile %} {% load crispy_forms_tags %} {% load set %} @@ -19,15 +20,33 @@ +{% block article_schema %} + itemscope itemtype="http://schema.org/Article" +{% endblock %} + + + {% block headline %} - <h1 {% if article.image %}class="illu"{% endif %}> + <h1 {% if article.image %}class="illu"{% endif %} itemprop="name"> {% if article.image %} - <img src="{{ article.image.article_illu.url }}" alt=""> + <img src="{{ article.image.article_illu.url }}" alt="" itemprop="thumbnailUrl"> {% endif %} {{ article.title }} </h1> + + {% if article.licence %} + <span class="license" itemprop="license"> + {{ article.licence }} + </span> + {% endif %} + + {% if article.description %} + <h2 class="subtitle"> + {{ article.description }} + </h2> + {% endif %} - <ul class="taglist"> + <ul class="taglist" itemprop="keywords"> {% for tag in tags.all %} <li> <a href="{% url "zds.article.views.index" %}?tag={{ tag.title }}"> @@ -36,13 +55,20 @@ <h1 {% if article.image %}class="illu"{% endif %}> </li> {% endfor %} </ul> + + <span class="pubdate"> + Publié + <time datetime="{{ article.pubdate|date:"c" }}" pubdate="pubdate" itemprop="datePublished"> + {{ article.pubdate|format_date }} + </time> + </span> <div class="authors"> - <span class="authors-label">Contributeurs : </span> + <span class="authors-label">Contributeur{{ authors.all|pluralize }} : </span> <ul> {% for member in authors.all %} <li> - {% include "misc/member_item.part.html" with avatar=True %} + {% include "misc/member_item.part.html" with avatar=True author=True %} </li> {% endfor %} </ul> @@ -51,62 +77,8 @@ <h1 {% if article.image %}class="illu"{% endif %}> -{% block sidebar_actions %} - {% if perms.article.change_article %} - <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Validation"> - <h3>Validation</h3> - <ul> - <li> - <a href="{{ article.get_absolute_url }}" class="ico-after offline blue"> - Version hors ligne - </a> - </li> - <li> - <a href="#unpublish" class="open-modal ico-after cross blue">Dépublier</a> - <form action="{% url "zds.article.views.modify" %}" method="post" class="modal modal-small" id="unpublish"> - <p> - <strong>Attention !</strong> Vous allez dépublier un article actuellement <strong>en ligne</strong>. - </p> - <button type="submit" name="invalid-article"> - Confirmer - </button> - <input type="hidden" name="article" value="{{ article.pk }}"> - <input type="hidden" name="version" value="{{ version }}"> - {% csrf_token %} - </form> - </li> - <li> - <a href="{% url "zds.article.views.history_validation" article.pk %}" class="ico-after history blue"> - Historique de validation - </a> - </li> - </ul> - </div> - {% elif user in authors.all %} - <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Gestion"> - <h3>Gestion</h3> - <ul> - <li> - <a href="{{ article.get_absolute_url }}" class="ico-after offline blue"> - Version hors ligne - </a> - </li> - </ul> - </div> - {% endif %} - - <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Télécharger"> - <h3>Télécharger</h3> - <ul> - <li> - <a href="{% url "zds.article.views.download" %}?article={{ article.pk }}" - class="ico-after download blue" - > - Archive - </a> - </li> - </ul> - </div> +{% block article_content_schema %} + itemprop="articleBody" {% endblock %} @@ -114,17 +86,26 @@ <h3>Télécharger</h3> {% block content %} {{ article.txt|safe }} - + {% include "article/includes/pager.part.html" %} {% endblock %} {% block content_after %} - <h3 class="reactions-title">Réactions</h3> + <h3 class="comments-title" id="reactions"> + {% if article.get_reaction_count > 0 %} + <span itemprop="commentCount"> + {{ article.get_reaction_count }} + </span> + réaction{{ article.get_reaction_count|pluralize }} + {% else %} + Aucune réaction + {% endif %} + </h3> - {% include "misc/pagination.part.html" with position="top" topic=article is_online=True %} + {% include "misc/pagination.part.html" with position="top" topic=article is_online=True anchor="reactions" %} {% for message in reactions %} @@ -159,11 +140,11 @@ <h3 class="reactions-title">Réactions</h3> {% endif %} - {% include "misc/message.part.html" with perms_change=perms.article.change_reaction topic=article %} + {% include "misc/message.part.html" with perms_change=perms.article.change_reaction topic=article comment_schema=True %} {% endfor %} - {% include "misc/pagination.part.html" with position="bottom" topic=article is_online=True %} + {% include "misc/pagination.part.html" with position="bottom" topic=article is_online=True anchor="reactions" %} @@ -173,3 +154,63 @@ <h3 class="reactions-title">Réactions</h3> {% include "misc/message_form.html" with member=user topic=article %} {% endblock %} + + + +{% block sidebar_actions %} + {% if perms.article.change_article %} + <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Validation"> + <h3>Validation</h3> + <ul> + <li> + <a href="{{ article.get_absolute_url }}" class="ico-after offline blue"> + Version hors ligne + </a> + </li> + <li> + <a href="#unpublish" class="open-modal ico-after cross blue">Dépublier</a> + <form action="{% url "zds.article.views.modify" %}" method="post" class="modal modal-small" id="unpublish"> + <p> + <strong>Attention !</strong> Vous allez dépublier un article actuellement <strong>en ligne</strong>. + </p> + <button type="submit" name="invalid-article"> + Confirmer + </button> + <input type="hidden" name="article" value="{{ article.pk }}"> + <input type="hidden" name="version" value="{{ version }}"> + {% csrf_token %} + </form> + </li> + <li> + <a href="{% url "zds.article.views.history_validation" article.pk %}" class="ico-after history blue"> + Historique de validation + </a> + </li> + </ul> + </div> + {% elif user in authors.all %} + <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Gestion"> + <h3>Gestion</h3> + <ul> + <li> + <a href="{{ article.get_absolute_url }}" class="ico-after offline blue"> + Version hors ligne + </a> + </li> + </ul> + </div> + {% endif %} + + <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Télécharger"> + <h3>Télécharger</h3> + <ul> + <li> + <a href="{% url "zds.article.views.download" %}?article={{ article.pk }}" + class="ico-after download blue" + > + Archive + </a> + </li> + </ul> + </div> +{% endblock %} diff --git a/templates/base.html b/templates/base.html index d1064c560e..bd057037e5 100644 --- a/templates/base.html +++ b/templates/base.html @@ -5,59 +5,88 @@ {% load captureas %} {% load date %} {% load set %} +{% load captureas %} {% load thumbnail %} <!DOCTYPE html> -<html class="no-js enable-mobile-menu wf-active"> +<html class="no-js enable-mobile-menu wf-active" lang="fr"> <head> <meta charset="utf-8"> <title> - {% block title %}{% endblock %} - {% block title_base %}{% endblock %} - • Zeste de Savoir + {% captureas title %} + {% captureas title_blocks %} + {% block title %}{% endblock %} + {% block title_base %}{% endblock %} + {% endcaptureas %} + {% if title_blocks %} + {{ title_blocks|safe }} + • + {% endif %} + Zeste de Savoir + {% endcaptureas %} + {{ title|safe }} </title> + <meta name="language" content="fr"> + <meta http-equiv="content-language" content="fr"> <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0"> + - {% block meta %}{% endblock %} - + {# Description #} + {% captureas description %} + {% block description %} + Zeste de Savoir est un site de partage de connaissances sur lequel vous trouverez des tutoriels de tous niveaux, des articles et des forums d'entraide animés par + et pour la communauté. + {% endblock %} + {% endcaptureas %} + <meta name="description" content="{{ description|safe }}"> + + + {% captureas image %} + {{ request.META.HTTP_HOST }}{% block image %}{% spaceless %} + {% static "images/apple-touch-icon-144x144-precomposed.png" %} + {% endspaceless %}{% endblock %} + {% endcaptureas %} + + + {# OpenGraph #} + <meta property="og:site_name" content="Zeste de Savoir"> + <meta property="og:title" content="{{ title|safe }}"> + <meta property="og:url" content="{{ request.build_absolute_uri }}"> + <meta property="og:language" content="fr_FR"> + <meta property="og:image:url" content="http://{{ image }}"> + <meta property="og:image:secure_url" content="https://{{ image }}"> + {% block opengraph %} + <meta property="og:type" content="website"> + {% endblock %} + + + {# Twitter cards #} + <meta property="twitter:domain" content="http://zestedesavoir.com/"> + <meta property="twitter:card" content="summary"> + <meta property="twitter:url" content="{{ request.build_absolute_uri }}"> + <meta property="twitter:title" content="{{ title|safe }}"> + <meta property="twitter:description" content="{{ description|safe }}"> + <meta property="twitter:site" content="@ZesteDeSavoir"> + <meta property="twitter:creator" content="@{% block twitter_creator %}ZesteDeSavoir{% endblock %}"> + <meta property="twitter:image" content="http://{{ image }}"> + + + {# Stylesheets #} {% if debug %} - <link rel="stylesheet" href="/static/css/main.css" /> + <link rel="stylesheet" href="/static/css/main.css"> {% else %} - <link rel="stylesheet" href="/static/css/main.min.css" /> - {% block source_content %}{% endblock %} + <link rel="stylesheet" href="/static/css/main.min.css"> + {% block canonical %}{% endblock %} {% endif %} - {# Webfont async loading #} - {# Do not put this code into a distant file #} - <!-- TODO : optimiser ça pour ne pas voir le changement de font à chaque page - <script> - WebFontConfig = { - google: { - families: ['Source Sans Pro:400,700', 'Source Code Pro:400,700', 'Merriweather:400,700'] - } - }; - - (function() { - var wf = document.createElement('script'); - wf.src = ('https:' == document.location.protocol ? 'https' : 'http') + - '://ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js'; - wf.type = 'text/javascript'; - wf.async = 'true'; - var s = document.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(wf, s); - })(); - </script> - --> - <!-- TEMP : load fonts from Google / Procude FOUT effect (see above) --> - <link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700|Source+Code+Pro:400,700|Merriweather:400,700' rel='stylesheet' type='text/css'> + {# Webfont async loading #} + <link href='//fonts.googleapis.com/css?family=Source+Sans+Pro:400,700|Source+Code+Pro:400,700|Merriweather:400,700' rel='stylesheet' type='text/css'> - {# for additionnal css in some pages #} - {% block extracss %}{% endblock %} {# favicons #} <link rel="shortcut icon" type="image/png" href="{% static "images/favicon.png" %}"> @@ -68,17 +97,20 @@ <link rel="apple-touch-icon" href="{% static "images/apple-touch-icon-precomposed.png" %}"> <link rel="apple-touch-icon" href="{% static "images/apple-touch-icon.png" %}"> + {# Fullsceen on iOS #} <meta name="apple-mobile-web-app-capable" content="yes"> - {# RSS links #} - <link rel="alternate" type="application/rss+xml" - title="Forum" href="{% url "post-feed-rss" %}"/> + {# RSS links #} + <link rel="alternate" type="application/rss+xml" title="Forum" href="{% url "post-feed-rss" %}"> </head> -<body class="{% block body_class %}{% endblock %}"> - <!--[if lt IE 7]> - <p class="chromeframe">Vous utilisez un navigateur <strong>dépassé</strong>. Merci de <a href="http://browsehappy.com/">mettre à jour celui-ci</a> ou <a href="http://www.google.com/chromeframe/?redirect=true">activer Google Chrome Frame</a> pour améliorer votre expérience.</p> +<body class="{% block body_class %}{% endblock %}" + itemscope + itemtype="http://schema.org/WebPage" +> + <!--[if lt IE 8]> + <p class="chromeframe">Vous utilisez un navigateur <strong>dépassé</strong>. Merci de <a href="http://browsehappy.com/">mettre à jour celui-ci</a> pour améliorer votre expérience.</p> <![endif]--> <div class="mobile-menu" id="mobile-menu"></div> @@ -98,12 +130,12 @@ </ul> <div id="cookies-banner"> - <p> - Ce site utilise Google Analytics. En continuant à naviguer, vous nous autorisez à déposer des cookies à des fins de mesure d'audience. Pour s'opposer à ce dépôt vous pouvez cliquer - <button id="reject-cookies">ici</button>. - <a href="/pages/cookies">En savoir plus</a> - <button id="accept-cookies">OK</button> - </p> + <span> + Ce site utilise Google Analytics. En poursuivant votre navigation sur ce site, vous nous autorisez à déposer des cookies à des fins de mesure d'audience. Pour s'opposer à ce dépôt vous pouvez cliquer + </span> + <button id="reject-cookies">ici</button>. + <a href="/pages/cookies">En savoir plus</a> + <button id="accept-cookies">OK</button> </div> <div class="header-container"> @@ -118,14 +150,14 @@ {% endcaptureas %} {# Logo #} - <h1 class="header-logo"> + <div class="header-logo"> <a href="{% url "zds.pages.views.home" %}" class="header-logo-link" data-title="{{ mobiletitle }}" > Zeste de Savoir </a> - </h1> + </div> {# Menu #} @@ -414,14 +446,16 @@ <h1 class="header-logo"> <div class="clearfix sub-header"> <div class="wrapper"> - <div class="breadcrumb"> - <ul> + <div class="breadcrumb" itemprop="breadcrumb"> + <ol> <li> - <a href="{% url "zds.pages.views.home" %}">Accueil</a> + <a href="{% url "zds.pages.views.home" %}" rel="home" itemprop="url"> + <span itemprop="title">Accueil</span> + </a> </li> {% block breadcrumb_base %}{% endblock %} {% block breadcrumb %}{% endblock %} - </ul> + </ol> </div> <div class="search header-right" id="search"> <form action="/rechercher"> @@ -437,8 +471,10 @@ <h1 class="header-logo"> <div class="main-container"> + {% block pre_content %} + {% endblock %} + <div class="main wrapper clearfix"> - <main class="content-container" role="main" id="content"> {% if messages %} {% for message in messages %} @@ -450,15 +486,20 @@ <h1 class="header-logo"> {% endif %} {% block content_out %} - <section class="content-wrapper"> - <h2>{% block headline %}{% endblock %}</h2> + {% captureas schema %} + {% block schema %}{% endblock %} + {% endcaptureas %} + <section class="content-wrapper" {{ schema }}> + <h1 {% if schema %}itemprop="name"{% endif %}> + {% block headline %}{% endblock %} + </h1> {% captureas headlinesub %} {% block headline_sub %}{% endblock %} {% endcaptureas %} {% if headlinesub %} - <h3 class="subtitle">{{ headlinesub|safe }}</h3> + <h2 class="subtitle" {% if schema %}itemprop="description"{% endif %}>{{ headlinesub|safe }}</h2> {% endif %} {% block content %}{% endblock %} @@ -466,11 +507,9 @@ <h3 class="subtitle">{{ headlinesub|safe }}</h3> {% endblock %} </main> - {% block sidebar %} - {% endblock %} - - </div> <!-- #main --> - </div> <!-- #main-container --> + {% block sidebar %}{% endblock %} + </div> + </div> @@ -497,13 +536,25 @@ <h3 class="subtitle">{{ headlinesub|safe }}</h3> + + + {# Google Tag Manager #} + <noscript id="gtm"> + <iframe src="//www.googletagmanager.com/ns.html?id=GTM-WH7642" + height="0" width="0" style="display:none;visibility:hidden"></iframe> + </noscript> + + + + + {# Javascript stuff start #} {% if debug %} - <script src="/static/js/vendors.js"></script> - <script src="/static/js/main.js"></script> + <script src="/static/js/vendors.js"></script> + <script src="/static/js/main.js"></script> {% else %} - <script src="/static/js/vendors.min.js"></script> - <script src="/static/js/main.min.js"></script> + <script src="/static/js/vendors.min.js"></script> + <script src="/static/js/main.min.js"></script> {% endif %} @@ -521,20 +572,5 @@ <h3 class="subtitle">{{ headlinesub|safe }}</h3> }); </script> <script type="text/javascript" src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> - - - - {# Google Tag Manager #} - <noscript> - <iframe src="//www.googletagmanager.com/ns.html?id=GTM-WH7642" - height="0" width="0" style="display:none;visibility:hidden"></iframe> - </noscript> - <script> - (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': - new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], - j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= - '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); - })(window,document,'script','dataLayer','GTM-WH7642'); - </script> </body> </html> diff --git a/templates/base_content_page.html b/templates/base_content_page.html index d56638efdf..a95f8b185d 100644 --- a/templates/base_content_page.html +++ b/templates/base_content_page.html @@ -3,11 +3,11 @@ {% block content_out %} - <article class="content-wrapper"> + <article class="content-wrapper" {% block article_schema %}{% endblock %}> <header> {% block headline %}{% endblock %} </header> - <section class="article-content"> + <section class="article-content" {% block article_centent_schema %}{% endblock %}> {% block content %}{% endblock %} </section> {% block content_after %}{% endblock %} diff --git a/templates/email/mp/new.html b/templates/email/mp/new.html index 16e6f1fd27..56addcbddc 100644 --- a/templates/email/mp/new.html +++ b/templates/email/mp/new.html @@ -9,11 +9,11 @@ <strong>{{ author }}</strong> vous a envoyé un message privé sur Zeste de Savoir. <br /> <br /> - Pour le lire, <a href="{{ url }}">cliquez ici</a> + Pour le lire, <a href="{{ url }}">cliquez ici</a>. <br /> <br /> <br /> Cordialement, <br /> L'équipe Zeste de Savoir -</body> \ No newline at end of file +</body> diff --git a/templates/forum/base.html b/templates/forum/base.html index ccc2c3e1c5..3ec9fc242d 100644 --- a/templates/forum/base.html +++ b/templates/forum/base.html @@ -7,7 +7,7 @@ {% block title_base %} - · Forums + • Forums {% endblock %} @@ -19,7 +19,11 @@ {% block breadcrumb_base %} - <li><a href="{% url "zds.forum.views.index" %}">Forums</a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{% url "zds.forum.views.index" %}" itemprop="url"> + <span itemprop="title">Forums</span> + </a> + </li> {% endblock %} @@ -59,6 +63,7 @@ <h3>Actions</h3> {% if user.is_authenticated %} <div class="mobile-menu-bloc mobile-all-links" data-title="Sujets suivis"> <h3>Sujets suivis</h3> + {% with follwedtopics=user|followed_topics %} {% for period, topics in follwedtopics.items %} <h4>{{ period|humane_delta }}</h4> @@ -78,15 +83,15 @@ <h4>{{ period|humane_delta }}</h4> </form> <a href="{% spaceless %} - {% with first_unread=topic.first_unread_post %} - {% if first_unread %} - {{ first_unread.get_absolute_url }} - {% else %} - {{ topic.last_read_post.get_absolute_url }} - {% endif %} - {% endwith %} - {% endspaceless %}" - class="{% if not topic|is_read %}unread{% endif %} + {% with first_unread=topic.first_unread_post %} + {% if first_unread %} + {{ first_unread.get_absolute_url }} + {% else %} + {{ topic.last_read_post.get_absolute_url }} + {% endif %} + {% endwith %} + {% endspaceless %}" + class="{% if not topic|is_read %}unread{% endif %} ico-after {% if topic.is_solved %} @@ -97,16 +102,16 @@ <h4>{{ period|humane_delta }}</h4> star yellow {% endif %} " - {% if topic.is_solved or topic.is_locked %} - data-prefix=" - {% if topic.is_solved %} - Résolu - {% endif %} - {% if topic.is_locked %} - Fermé - {% endif %} - " - {% endif %} + {% if topic.is_solved or topic.is_locked %} + data-prefix=" + {% if topic.is_solved %} + Résolu + {% endif %} + {% if topic.is_locked %} + Fermé + {% endif %} + " + {% endif %} > {% if not topic|is_read %} <span class="a11y">Non-lu :</span> @@ -122,7 +127,7 @@ <h4>{{ period|humane_delta }}</h4> {% endwith %} <span class="topic-last-answer"> Dernière réponse - {{ answer.pubdate|format_date }} + {{ answer.pubdate|format_date:True }} par <em>{{ answer.author.username }}</em> </span> @@ -138,9 +143,11 @@ <h4>{{ period|humane_delta }}</h4> </ul> {% endfor %} - {% if topics|length <= 0 %} + {% if follwedtopics.items|length <= 0 %} <ul> - <li class="inactive"><em>Aucun sujet suivi</em></li> + <li class="inactive"> + <span class="disabled">Aucun sujet suivi</span> + </li> </ul> {% endif %} {% endwith %} diff --git a/templates/forum/category/forum.html b/templates/forum/category/forum.html index 7886d85edd..cd337c146c 100644 --- a/templates/forum/category/forum.html +++ b/templates/forum/category/forum.html @@ -4,19 +4,48 @@ {% block title %} {{ forum.title }} + {% if request.GET.filter == "solve" %} + / Sujets résolus + {% elif request.GET.filter == "unsolve" %} + / Sujets non-résolus + {% endif %} {% endblock %} {% block breadcrumb %} - <li><a href="{{ forum.category.get_absolute_url }}">{{ forum.category.title }}</a></li> - <li>{{ forum.title }}</li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ forum.category.get_absolute_url }}" itemprop="url"> + <span itemprop="title">{{ forum.category.title }}</span> + </a> + </li> + + {% if request.GET.filter %} + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ forum.get_absolute_url }}" itemprop="url"> + <span itemprop="title">{{ forum.title }}</span> + </a> + </li> + {% else %} + <li>{{ forum.title }}</li> + {% endif %} + + {% if request.GET.filter == "solve" %} + <li>Sujets résolus</li> + {% elif request.GET.filter == "unsolve" %} + <li>Sujets non-résolus</li> + {% endif %} {% endblock %} {% block headline %} {{ forum.title }} + {% if request.GET.filter == "solve" %} + / Sujets résolus + {% elif request.GET.filter == "unsolve" %} + / Sujets non-résolus + {% endif %} {% endblock %} @@ -39,7 +68,7 @@ {% include "misc/pagination.part.html" with position="top" %} {% if sticky_topics %} - <div class="topic-list navigable-list"> + <div class="topic-list navigable-list" itemscope itemtype="http://schema.org/ItemList"> {% for topic in sticky_topics %} {% include "forum/includes/topic_row.part.html" %} {% endfor %} @@ -47,7 +76,7 @@ {% endif %} {% if topics %} - <div class="topic-list navigable-list"> + <div class="topic-list navigable-list" itemscope itemtype="http://schema.org/ItemList"> {% for topic in topics %} {% include "forum/includes/topic_row.part.html" %} {% endfor %} @@ -71,25 +100,27 @@ <h3>Filtres</h3> <ul> <li> <a href="{% url "zds.forum.views.details" forum.category.slug forum.slug %}?filter=solve" - class="ico-after tick green" + class="ico-after tick green {% if request.GET.filter == "solve" %}unread{% endif %}" > Sujets résolus </a> </li> <li> <a href="{% url "zds.forum.views.details" forum.category.slug forum.slug %}?filter=unsolve" - class="ico-after tick blue" + class="ico-after tick blue {% if request.GET.filter == "unsolve" %}unread{% endif %}" > Sujets non résolus </a> </li> - <li> - <a href="{% url "zds.forum.views.details" forum.category.slug forum.slug %}" - class="ico-after cross blue" - > - Annuler le filtre - </a> - </li> + {% if request.GET.filter %} + <li> + <a href="{% url "zds.forum.views.details" forum.category.slug forum.slug %}" + class="ico-after cross blue" + > + Annuler le filtre + </a> + </li> + {% endif %} </ul> </div> {% endblock %} \ No newline at end of file diff --git a/templates/forum/category/index.html b/templates/forum/category/index.html index 0e7d6f5d0f..75a5f3b414 100644 --- a/templates/forum/category/index.html +++ b/templates/forum/category/index.html @@ -21,7 +21,7 @@ {% block content %} - <div class="content-wrapper topic-list topic-list-small forum-list navigable-list"> + <div class="content-wrapper topic-list topic-list-small forum-list navigable-list" itemscope itemtype="http://schema.org/ItemList"> {% include "forum/includes/forums.part.html" %} </div> {% endblock %} \ No newline at end of file diff --git a/templates/forum/find/post.html b/templates/forum/find/post.html index 33a7151ccf..baeefc9733 100644 --- a/templates/forum/find/post.html +++ b/templates/forum/find/post.html @@ -12,16 +12,16 @@ {% block headline %} - <h2 class="box forum">Messages postés par "{{ usr.username }}""</h2> + Messages postés par "{{ usr.username }}" {% endblock %} {% block breadcrumb %} {% with profile=usr|profile %} - <li ><a href="{{ profile.get_absolute_url }}">{{ usr.username }}</a></li> + <li><a href="{{ profile.get_absolute_url }}">{{ usr.username }}</a></li> {% endwith %} - <li><a href="{% url "zds.forum.views.find_post" usr.username %}">Messages postés</a></li> + <li><a href="{% url "zds.forum.views.find_post" usr.pk %}">Messages postés</a></li> <li>Recherche</li> {% endblock %} @@ -30,35 +30,41 @@ <h2 class="box forum">Messages postés par "{{ usr.username }}""</h2> {% block content %} {% include "misc/pagination.part.html" with position="top" %} - <div class="row"> - <table> - <thead> - <tr> - <th width="15%">Sujet</th> - <th width="10%">Quand</th> - <th width="30%">Extrait</th> - </tr> - </thead> - <tbody> - {% for post in posts %} - <tr> - <td> - <div class="forum-entry-title {% if user.is_authenticated %} {% if topic.never_read %} unread {% endif %} {% endif %}"> - <a href="{{ post.get_absolute_url }}">{{ post.topic.title }} </a> - {% if post.topic.subtitle %} <p> {{ post.topic.subtitle }} </p> {% endif %} - </div> - </td> - <td> - {{ post.pubdate|format_date }} - </td> - <td> - {{ post.text|truncatechars:200|emarkdown|striptags }} - </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> + {% if posts %} + <div class="row"> + <table> + <thead> + <tr> + <th width="15%">Sujet</th> + <th width="10%">Quand</th> + <th width="30%">Extrait</th> + </tr> + </thead> + <tbody> + {% for post in posts %} + <tr> + <td> + <div class="forum-entry-title {% if user.is_authenticated %} {% if topic.never_read %} unread {% endif %} {% endif %}"> + <a href="{{ post.get_absolute_url }}">{{ post.topic.title }} </a> + {% if post.topic.subtitle %} <p> {{ post.topic.subtitle }} </p> {% endif %} + </div> + </td> + <td> + {{ post.pubdate|format_date }} + </td> + <td> + {{ post.text|truncatechars:200|emarkdown|striptags }} + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% else %} + <p> + Aucun message n'a été posté par {{ usr.username }}. + </p> + {% endif %} {% include "misc/pagination.part.html" with position="bottom" %} {% endblock %} diff --git a/templates/forum/find/topic.html b/templates/forum/find/topic.html index 2b10a6e958..6567297291 100644 --- a/templates/forum/find/topic.html +++ b/templates/forum/find/topic.html @@ -12,7 +12,7 @@ {% block headline %} - <h2 class="box forum">Sujets crées par "{{ usr.username }}""</h2> + Sujets crées par "{{ usr.username }}" {% endblock %} @@ -21,7 +21,7 @@ <h2 class="box forum">Sujets crées par "{{ usr.username }}""</h2> {% with profile=usr|profile %} <li><a href="{{ profile.get_absolute_url }}">{{ usr.username }}</a></li> {% endwith %} - <li><a href="{% url "zds.forum.views.find_topic" usr.username %}">Sujets crées</a></li> + <li><a href="{% url "zds.forum.views.find_topic" usr.pk %}">Sujets crées</a></li> <li>Recherche</li> {% endblock %} @@ -30,33 +30,39 @@ <h2 class="box forum">Sujets crées par "{{ usr.username }}""</h2> {% block content %} {% include "misc/pagination.part.html" with position="top" %} - <table> - <thead> - <tr> - <th width="15%">Sujet</th> - <th width="10%">Quand</th> - <th width="30%">Extrait</th> - </tr> - </thead> - <tbody> - {% for topic in topics %} - <tr> - <td> - <div class="forum-entry-title {% if user.is_authenticated %} {% if topic.never_read %} unread {% endif %} {% endif %}"> - <a href="{{ topic.get_absolute_url }}">{{ topic.title }} </a> - {% if topic.subtitle %} <p> {{ topic.subtitle }} </p> {% endif %} - </div> - </td> - <td> - {{ topic.pubdate|format_date }} - </td> - <td> - {{ topic.first_post.text|truncatechars:200|emarkdown|striptags }} - </td> - </tr> - {% endfor %} - </tbody> - </table> + {% if topics %} + <table> + <thead> + <tr> + <th width="15%">Sujet</th> + <th width="10%">Quand</th> + <th width="30%">Extrait</th> + </tr> + </thead> + <tbody> + {% for topic in topics %} + <tr> + <td> + <div class="forum-entry-title {% if user.is_authenticated %} {% if topic.never_read %} unread {% endif %} {% endif %}"> + <a href="{{ topic.get_absolute_url }}">{{ topic.title }} </a> + {% if topic.subtitle %} <p> {{ topic.subtitle }} </p> {% endif %} + </div> + </td> + <td> + {{ topic.pubdate|format_date }} + </td> + <td> + {{ topic.first_post.text|truncatechars:200|emarkdown|striptags }} + </td> + </tr> + {% endfor %} + </tbody> + </table> + {% else %} + <p> + Aucun sujet n'a été créé par {{ usr.username }}. + </p> + {% endif %} {% include "misc/pagination.part.html" with position="bottom" %} {% endblock %} diff --git a/templates/forum/find/topic_by_tag.html b/templates/forum/find/topic_by_tag.html index f274a464e8..e1c81d9579 100644 --- a/templates/forum/find/topic_by_tag.html +++ b/templates/forum/find/topic_by_tag.html @@ -5,19 +5,19 @@ {% block title %} - Tag : {{tag.title}} + Tag : {{ tag.title }} {% endblock %} {% block breadcrumb %} - <li>Tag : {{tag.title}}</li> + <li>Tag : {{ tag.title }}</li> {% endblock %} {% block headline %} - Tag : {{tag.title}} + Tag : {{ tag.title }} {% endblock %} diff --git a/templates/forum/includes/forums.part.html b/templates/forum/includes/forums.part.html index 8b45deabb8..273c1dfbde 100644 --- a/templates/forum/includes/forums.part.html +++ b/templates/forum/includes/forums.part.html @@ -4,36 +4,36 @@ {% for forum in forums %} - <div class="topic navigable-elem"> - <div class="topic-description {% if user.is_authenticated and not forum.is_read %}unread{% endif %}"> - <a href="{{ forum.get_absolute_url }}" class="navigable-link"> - <h4 class="topic-title"> - {{ forum.title }} - </h4> - </a> - <span class="topic-subtitle"> - {{ forum.subtitle }} - </span> - </div> - <p class="topic-answers"> - <span title="Nombre de sujets dans le forum"> - {{ forum.get_topic_count }} - </span> - <span title="Nombre de messages dans le forum"> - {{ forum.get_post_count }} - </span> - </p> - <p class="topic-last-answer"> - {% with last_message=forum.get_last_message %} - {% if last_message %} - <a href="{{ last_message.topic.last_read_post.get_absolute_url }}"> - <span class="forum-last-message">Dernier message :</span> - <span class="forum-last-message-title">{{ last_message.topic.title }}</span> - </a> - {% else %} - <span class="topic-no-last-answer">Aucun sujet</span> - {% endif %} - {% endwith %} - </p> + <div class="topic navigable-elem"> + <div class="topic-description {% if user.is_authenticated and not forum.is_read %}unread{% endif %}"> + <a href="{{ forum.get_absolute_url }}" class="navigable-link"> + <h4 class="topic-title" itemprop="itemListElement"> + {{ forum.title }} + </h4> + </a> + <span class="topic-subtitle"> + {{ forum.subtitle }} + </span> </div> + <p class="topic-answers"> + <span title="Nombre de sujets dans le forum"> + {{ forum.get_topic_count }} + </span> + <span title="Nombre de messages dans le forum"> + {{ forum.get_post_count }} + </span> + </p> + <p class="topic-last-answer"> + {% with last_message=forum.get_last_message %} + {% if last_message %} + <a href="{{ last_message.topic.last_read_post.get_absolute_url }}"> + <span class="forum-last-message">Dernier message :</span> + <span class="forum-last-message-title">{{ last_message.topic.title }}</span> + </a> + {% else %} + <span class="topic-no-last-answer">Aucun sujet</span> + {% endif %} + {% endwith %} + </p> + </div> {% endfor %} \ No newline at end of file diff --git a/templates/forum/includes/topic_row.part.html b/templates/forum/includes/topic_row.part.html index 32465340c3..25fbc7ab47 100644 --- a/templates/forum/includes/topic_row.part.html +++ b/templates/forum/includes/topic_row.part.html @@ -22,7 +22,7 @@ {% with profile=topic.author|profile %} <div class="topic-description"> <a href="{{ topic.get_absolute_url }}" class="topic-title-link navigable-link"> - <span class="topic-title">{{ topic.title }}</span> + <span class="topic-title" itemprop="itemListElement">{{ topic.title }}</span> <span class="topic-subtitle">{{ topic.subtitle }}</span> </a> <p class="topic-members"> @@ -30,8 +30,9 @@ <em> {% include "misc/member_item.part.html" with member=topic.author %} </em> + <span class="topic-members-label">{{ topic.pubdate|format_date }}</span> {% if topic.tags.all %} - - Tags : + - Tag{{ topic.tags.all|pluralize }} : {% for tag in topic.tags.all %} <a href="{% url "zds.forum.views.find_topic_by_tag" tag.pk tag.slug %}" class="topic-tag">{{ tag.title }}</a> {% endfor %} @@ -42,7 +43,7 @@ <p class="topic-answers"> {% with nb_post=topic.get_post_count %} {% if nb_post > 1 %} - {{ nb_post|add:"-1" }} réponse{% if nb_post > 2 %}s{% endif %} + {{ nb_post|add:"-1" }} réponse{{ nb_post|add:"-1"|pluralize }} {% endif %} {% endwith %} </p> diff --git a/templates/forum/index.html b/templates/forum/index.html index f0c04a8fd1..42e3538b52 100644 --- a/templates/forum/index.html +++ b/templates/forum/index.html @@ -7,12 +7,9 @@ -{% block meta %} - <meta name="description" content="Les forums vous permettent de venir poser - vos questions aux autres membres mais aussi de vous entraîner ou tout - simplement de discuter. Cela fonctionne également dans l'autre sens : vous - pouvez ici aider les autres et proposer des exercices au reste de la - communauté."> +{% block description %} + Les forums vous permettent de venir poser vos questions aux autres membres mais aussi de vous entraîner ou tout + simplement de discuter. {% endblock %} @@ -37,11 +34,13 @@ proposer des exercices au reste de la communauté. </p> - <div class="content-wrapper topic-list topic-list-small forum-list navigable-list"> + <div class="content-wrapper topic-list topic-list-small forum-list navigable-list" itemscope itemtype="http://schema.org/ItemList"> + <meta itemprop="itemListOrder" content="Unordered"> + {% for title, forums in categories.items %} - <h3 class="group-title content-wrapper"> + <h2 class="group-title content-wrapper"> {{ title }} - </h3> + </h2> {% include "forum/includes/forums.part.html" with forum=forums %} {% endfor %} </div> diff --git a/templates/forum/topic/followed.html b/templates/forum/topic/followed.html index 48d5530c01..0fc021f77e 100644 --- a/templates/forum/topic/followed.html +++ b/templates/forum/topic/followed.html @@ -3,19 +3,19 @@ {% block title %} - Notifications du Forum + Sujets suivis {% endblock %} {% block breadcrumb %} - <li>Notifications du Forum</li> + <li>Sujets suivis</li> {% endblock %} {% block headline %} - Notifications du Forum + Sujets suivis {% endblock %} diff --git a/templates/forum/topic/index.html b/templates/forum/topic/index.html index b077c3dd37..6769f68690 100644 --- a/templates/forum/topic/index.html +++ b/templates/forum/topic/index.html @@ -14,9 +14,25 @@ {% block breadcrumb %} - <li><a href="{{ topic.forum.category.get_absolute_url }}">{{ topic.forum.category.title }}</a></li> - <li><a href="{{ topic.forum.get_absolute_url }}">{{ topic.forum.title }}</a></li> - <li>{{ topic.title }}</li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ topic.forum.category.get_absolute_url }}" itemprop="url"> + <span itemprop="title">{{ topic.forum.category.title }}</span> + </a> + </li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ topic.forum.get_absolute_url }}" itemprop="url"> + <span itemprop="title">{{ topic.forum.title }}</span> + </a> + </li> + <li> + {{ topic.title }} + </li> +{% endblock %} + + + +{% block schema %} + itemscope itemtype="http://schema.org/Question" {% endblock %} @@ -34,11 +50,17 @@ {% block content %} - <ul class="taglist"> - {% for tag in topic.tags.all %} - <li><a href="{% url "zds.forum.views.find_topic_by_tag" tag.pk tag.slug %}">{{ tag.title }}</a></li> - {% endfor %} - </ul> + {% if topic.tags.all %} + <ul class="taglist" itemprop="keywords"> + {% for tag in topic.tags.all %} + <li> + <a href="{% url "zds.forum.views.find_topic_by_tag" tag.pk tag.slug %}"> + {{ tag.title }} + </a> + </li> + {% endfor %} + </ul> + {% endif %} {% include "misc/pagination.part.html" with position="top" %} @@ -80,7 +102,13 @@ {% set False as is_repeated_message %} {% endif %} - {% include "misc/message.part.html" with perms_change=perms.forum.change_topic %} + {% if forloop.first and nb = 1 %} + {% set True as answer_schema %} + {% else %} + {% set False as answer_schema %} + {% endif %} + + {% include "misc/message.part.html" with perms_change=perms.forum.change_topic answer_schema=answer_schema %} {% endfor %} </div> diff --git a/templates/forum/topic/new.html b/templates/forum/topic/new.html index 49efc26a3c..7dc210217e 100644 --- a/templates/forum/topic/new.html +++ b/templates/forum/topic/new.html @@ -17,8 +17,16 @@ {% block breadcrumb %} - <li><a href="{{ forum.category.get_absolute_url }}">{{ forum.category.title }}</a></li> - <li><a href="{{ forum.get_absolute_url }}">{{ forum.title }}</a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ forum.category.get_absolute_url }}" itemprop="url"> + <span itemprop="title">{{ forum.category.title }}</span> + </a> + </li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ forum.get_absolute_url }}" itemprop="url"> + <span itemprop="title">{{ forum.title }}</span> + </a> + </li> <li>Nouveau sujet</li> {% endblock %} diff --git a/templates/gallery/base.html b/templates/gallery/base.html old mode 100644 new mode 100755 index 7824c0c23c..9df3d748dd --- a/templates/gallery/base.html +++ b/templates/gallery/base.html @@ -3,7 +3,7 @@ {% block title_base %} - · Galeries + • Galeries {% endblock %} @@ -21,5 +21,6 @@ <a href="{% url "zds.gallery.views.new_gallery" %}" class="new-btn ico-after more blue"> Ajouter une galerie </a> + {% block sidebar_actions %}{% endblock %} </aside> {% endblock %} \ No newline at end of file diff --git a/templates/gallery/gallery/details.html b/templates/gallery/gallery/details.html index 77ba1b5c96..5a6cbe590b 100644 --- a/templates/gallery/gallery/details.html +++ b/templates/gallery/gallery/details.html @@ -65,7 +65,7 @@ </ul> </div> - <form id="form" name="form" method="post" action="{% url "zds.gallery.views.modify_image" %}"> + <form id="form" name="form" method="post" action="{% url "zds.gallery.views.delete_image" %}"> <input type="hidden" name="gallery" value="{{ gallery.pk }}"> <button class="btn btn-cancel" name="delete_multi">Supprimer la sélection</button> <button class="toggle-gallery-view btn btn-grey" type="button" title="Alterner entre les modes de vue grille et liste"> diff --git a/templates/gallery/gallery/list.html b/templates/gallery/gallery/list.html old mode 100644 new mode 100755 index 7da4bffdbc..a51773d5a0 --- a/templates/gallery/gallery/list.html +++ b/templates/gallery/gallery/list.html @@ -21,49 +21,74 @@ {% block content %} - <form id="form" name="form" method="post" action="{% url "zds.gallery.views.modify_gallery" %}"> - <div class="topic-list topic-list-small navigable-list"> - {% for gallery in galleries %} - <div class="topic navigable-elem"> - <div class="topic-infos"> - <input - name="items" - type="checkbox" - {% if not gallery.is_write %} - disabled="true" - {% endif %} - value="{{ gallery.gallery.pk }}" - > - </div> - <div class="topic-description"> - <a href="{{ gallery.gallery.get_absolute_url }}" class="topic-title-link navigable-link"> - <span class="topic-title"> - {{ gallery.gallery.title }} - </span> - <span class="topic-subtitle"> - {{ gallery.gallery.subtitle|default:"Pas de description" }} - </span> - </a> - </div> - <p class="topic-answers"> - {% with img_count=gallery.gallery.get_images.count %} - {% if img_count == 0 %} - Aucune image - {% elif img_count == 1 %} - 1 image - {% else %} - {{ img_count }} images - {% endif %} - {% endwith %} - </p> + <div class="topic-list topic-list-small navigable-list"> + {% for gallery in galleries %} + <div class="topic navigable-elem"> + <div class="topic-infos"> + <input + name="items" + type="checkbox" + {% if not gallery.is_write %} + disabled="true" + {% endif %} + value="{{ gallery.gallery.pk }}" + form="delete-galleries" + > + </div> + <div class="topic-description"> + <a href="{{ gallery.gallery.get_absolute_url }}" class="topic-title-link navigable-link"> + <span class="topic-title"> + {{ gallery.gallery.title }} + </span> + <span class="topic-subtitle"> + {{ gallery.gallery.subtitle|default:"Pas de description" }} + </span> + </a> </div> - {% endfor %} - </div> + <p class="topic-answers"> + {% with img_count=gallery.gallery.get_images.count %} + {% if img_count == 0 %} + Aucune image + {% elif img_count == 1 %} + 1 image + {% else %} + {{ img_count }} images + {% endif %} + {% endwith %} + </p> + </div> + {% endfor %} + </div> + + {% if galleries|length = 0 %} + <p> + Vous n'avez pas encore <a href="{% url "zds.gallery.views.new_gallery" %}">ajouté de galerie</a>. + </p> + {% endif %} +{% endblock %} + - <button type="submit" name="delete_multi" class="btn btn-cancel"> - Supprimer la séléction - </button> - {% csrf_token %} - </form> +{% block sidebar_actions %} + {% if galleries|length > 0 %} + <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Actions"> + <h3>Actions</h3> + <ul> + <li> + <a href="#delete-galleries" class="open-modal ico-after cross red"> + Supprimer les galeries sélectionnées + </a> + + <form action="{% url "zds.gallery.views.modify_gallery" %}" method="post" id="delete-galleries" class="modal modal-small"> + <p> + Attention, vous vous appretez à supprimer toutes les galeries sélectionnées. + </p> + + {% csrf_token %} + <button type="submit" name="delete_multi" class="btn">Confirmer</button> + </form> + </li> + </ul> + </div> + {% endif %} {% endblock %} diff --git a/templates/home.html b/templates/home.html index 1fe42f6f97..ca2ab9f2bc 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load staticfiles %} {% load emarkdown %} {% load date %} {% load interventions %} @@ -7,73 +8,119 @@ -{% block title %} - Accueil -{% endblock %} +{% block body_class %}home content-cols{% endblock %} -{% block meta %} - {# TODO : Make a better description #} - <meta name="description" content="Bienvenue sur Zeste de Savoir, une communauté"> +{% block pre_content %} + {% if user.is_authenticated %} + <section class="home-quote home-simple-quote"> + <div class="home-wrapper"> + <img src="{% static "images/clem-home-quote.png" %}" alt=""> + <blockquote> + « {{ quote }} » + </blockquote> + </div> + </section> + {% else %} + <section class="home-quote home-full-description"> + <div class="home-wrapper"> + <img src="{% static "images/clem-home.png" %}" alt="" width="214" height="209"> + <div class="home-full-description-text"> + <h2>Zeste de Savoir, la connaissance pour tous et sans pépins</h2> + + <blockquote> + « {{ quote }} » + </blockquote> + + <p> + Zeste de Savoir est un site de <strong>partage de connaissances</strong> sur lequel vous trouverez des <a href="http://zestedesavoir.com/tutoriels/">tutoriels de tous niveaux</a>, des <a href="http://zestedesavoir.com/articles/">articles</a> et des <a href="http://zestedesavoir.com/forums/">forums d'entraide</a> animés par et pour la communauté. Les sujets abordés sont, pour l'instant, l'informatique et les sciences, mais nous n'attendons que vous pour élargir les domaines présentés ! + </p> + <p> + Tous les membres peuvent écrire et <strong>publier des tutoriels et articles sur le site</strong>. Pour assurer la qualité et la pédagogie du contenu, l'équipe du site valide chaque cours avant publication. + </p> + <p> + Tout cela est <strong>entièrement gratuit et garanti sans publicité</strong>, le site est géré et financé par une <a href="http://zestedesavoir.com/pages/association/">association</a> a but non lucratif. + </p> + </div> + </div> + </section> + {% endif %} + + + + <section class="search-box"> + <form action="/rechercher" class="home-wrapper"> + <label for="search-box-field"> + Tu cherches quelque chose ? + </label> + <input type="text" name="q" placeholder="Exemples : Mathématiques, UDK, HTML, PHP, Java, ..." id="search-box-field"> + <button type="submit" class="ico-after search-submit" title="Lancer la recherche"> + OK + </button> + </form> + </section> {% endblock %} -{% block body_class %}home content-cols{% endblock %} - - - {% block content_out %} - <section class="content-col-2 tutorial-list"> - <h2 class="ico-after ico-articles">Derniers articles</h2> - - {% if last_articles %} - {% for article in last_articles %} - <article> - {% if article.image %} - <a href="{{ article.get_absolute_url_online }}" tabindex="-1"> - <img src="{{ article.image.article_illu.url }}" alt="" class="tutorial-img avatar"> + <section class="content-col-2 tutorial-list" itemscope itemtype="http://schema.org/ItemList"> + <h2 class="ico-after ico-articles" itemprop="name"> + Derniers articles + </h2> + + <meta itemprop="itemListOrder" content="Descending"> + + {% for article in last_articles %} + <article> + {% if article.image %} + <a href="{{ article.get_absolute_url_online }}" tabindex="-1"> + <img src="{{ article.image.article_illu.url }}" alt="" class="tutorial-img avatar"> + </a> + {% endif %} + <div class="tutorial-infos {% if not article.image %}no-illu{% endif %}"> + <h3> + <a href="{{ article.get_absolute_url_online }}" title="{{ article.title }}" itemprop="itemListElement"> + {{ article.title }} </a> - {% endif %} - <div class="tutorial-infos"> - <h3> - <a href="{{ article.get_absolute_url_online }}" title="{{ article.title }}"> - {{ article.title }} - </a> - </h3> - - <span class="article-metadata"> - {{ article.pubdate|format_date|capfirst }} - - <a href="{{ article.get_last_reaction.get_absolute_url }}"> - {% if article.get_reaction_count == 0 %} - Aucune réaction - {% elif article.get_reaction_count == 1 %} - 1 réaction - {% else %} - {{ article.get_reaction_count }} réactions - {% endif %} - </a> - </span> - </div> - - <p class="resume"> - {{ article.description }} - </p> - </article> - {% endfor %} - {% endif %} + </h3> + + <span class="article-metadata"> + {{ article.pubdate|format_date|capfirst }} - + <a href=" + {% if article.get_reaction_count == 0 %} + {{ link }}#reactions + {% else %} + {{ article.get_last_reaction.get_absolute_url }} + {% endif %} + "> + {% if article.get_reaction_count == 0 %} + Aucune réaction + {% else %} + {{ article.get_reaction_count }} réaction{{ article.get_reaction_count|pluralize }} + {% endif %} + </a> + </span> + </div> + + <p class="resume"> + {{ article.description }} + </p> + </article> + {% endfor %} </section> - <section class="content-col-2 tutorial-list"> - <h2 class="ico-after ico-tutorials">Derniers tutoriels</h2> + <section class="content-col-2 tutorial-list" itemscope itemtype="http://schema.org/ItemList"> + <h2 class="ico-after ico-tutorials" itemprop="name"> + Derniers tutoriels + </h2> - {% if last_tutorials %} - {% for tutorial in last_tutorials %} - {% include 'tutorial/includes/tutorial_item.part.html' %} - {% endfor %} - {% endif %} + <meta itemprop="itemListOrder" content="Descending"> + {% for tutorial in last_tutorials %} + {% include 'tutorial/includes/tutorial_item.part.html' %} + {% endfor %} </section> {% endblock %} diff --git a/templates/member/base.html b/templates/member/base.html index 7e7ebaa50f..287279fd30 100644 --- a/templates/member/base.html +++ b/templates/member/base.html @@ -3,7 +3,7 @@ {% block title_base %} - · Membres + • Membres {% endblock %} diff --git a/templates/member/login.html b/templates/member/login.html index a56292d807..cf89a73f3b 100644 --- a/templates/member/login.html +++ b/templates/member/login.html @@ -23,7 +23,7 @@ {% block content_out %} <section class="small-content-wrapper"> - <h2>Connexion</h2> + <h1>Connexion</h1> {% if not user.is_authenticated %} {% if error %} diff --git a/templates/member/profile.html b/templates/member/profile.html index f3830f762b..96cad5f304 100644 --- a/templates/member/profile.html +++ b/templates/member/profile.html @@ -25,9 +25,9 @@ {% block content_out %} <section class="content-col-2"> - <h2> - {{ usr.username }} - </h2> + <h1> + Profil de {{ usr.username }} + </h1> <img src="{{ profile.get_avatar_url }}" alt="" class="avatar"> @@ -61,7 +61,7 @@ <h3>Signature</h3> {% if topics %} - <h2>Dernier sujets crées</h2> + <h2>Derniers sujets créés</h2> {% for topic in topics %} <h4><a href="{{ topic.get_absolute_url }}">{{ topic.title }}</a></h4> <h5>{{ topic.subtitle }}</h5> diff --git a/templates/misc/member_item.part.html b/templates/misc/member_item.part.html index 58576c1d84..6b0de93951 100644 --- a/templates/misc/member_item.part.html +++ b/templates/misc/member_item.part.html @@ -3,11 +3,22 @@ {% with profile=member|profile %} - <a href="{{ member.get_absolute_url }}" class="member-item">{% spaceless %} + <a + href="{{ member.get_absolute_url }}" + class="member-item" + itemscope + itemtype="http://schema.org/Person" + {% if author %} + itemprop="author" + {% endif %} + >{% spaceless %} {% if avatar %} - <img src="{{ profile.get_avatar_url }}" alt="" class="avatar"> + <img src="{{ profile.get_avatar_url }}" alt="" class="avatar" itemprop="image"> {% endif %} - {{ member.username }} + + <span itemprop="name"> + {{ member.username }} + </span> {% if info %} <span class="info">({{ info }})</span> diff --git a/templates/misc/message.part.html b/templates/misc/message.part.html index 97a375875e..d643f45cd5 100644 --- a/templates/misc/message.part.html +++ b/templates/misc/message.part.html @@ -5,13 +5,32 @@ -<article class="topic-message {% if message.is_useful %}helpful{% endif %} {% if is_repeated_message %}repeated{% endif %}"> - {% if topic.author = message.author and helpful_link %} - {% set True as is_author %} - {% else %} - {% set False as is_author %} - {% endif %} +{% if topic.author = message.author and helpful_link %} + {% set True as is_author %} +{% else %} + {% set False as is_author %} +{% endif %} + + +<article + class=" + topic-message + {% if message.is_useful %}helpful{% endif %} + {% if is_repeated_message %}repeated{% endif %} + " + {% if comment_schema %} + itemscope + itemtype="http://schema.org/Comment" + itemprop="comment" + {% elif answer_schema %} + itemscope + itemtype="http://schema.org/Answer" + {% if message.is_useful or message.like > message.dislike %} + itemprop="{% if message.is_useful %}acceptedAnswer{% endif %} {% if message.like > message.dislike %}suggestedAnswer{% endif %}" + {% endif %} + {% endif %} +> {% include "misc/message_user.html" with member=message.author push_badge=is_author %} @@ -114,8 +133,15 @@ {% endif %} <div class="message-metadata"> - <a href="{{ message.author.get_absolute_url }}" class="username">{{ message.author.username }}</a> - <a href="#p{{ message.pk }}" id="p{{ message.id }}" class="date" title="{{ message.pubdate|tooltip_date|capfirst }}">{{ message.pubdate|format_date|capfirst }}</a> + <a href="{{ message.author.get_absolute_url }}" class="username" itemprop="author" itemscope itemtype="http://schema.org/Person"> + <span itemprop="name">{{ message.author.username }}</span> + </a> + + <a href="#p{{ message.pk }}" id="p{{ message.id }}" class="date" title="{{ message.pubdate|tooltip_date|capfirst }}"> + <time itemprop="dateCreated" datetime="{{ message.pubdate|date:"c" }}"> + {{ message.pubdate|format_date|capfirst }} + </time> + </a> </div> <div class="message-content"> @@ -131,7 +157,9 @@ {% endif %} {% if message.is_visible != False %} - {{ message.text_html|safe }} + <div itemprop="text"> + {{ message.text_html|safe }} + </div> {% elif perms_change %} <div class="message-hidden-content"> {{ message.text_html|safe }} @@ -140,14 +168,20 @@ {% if message.is_visible = False %} <p class="message-hidden"> - Masqué par {{ message.editor }} : {{ message.text_hidden }} + Masqué par {{ message.editor }} + {% if message.text_hidden %} + : {{ message.text_hidden }} + {% endif %} </p> {% elif message.update %} <p class="message-edited ico-after edit"> - Edité {{ message.update|format_date }} + Edité {% if message.editor %} par {{ message.editor }} {% endif %} + <time itemprop="dateModified" datetime="{{ message.update|date:"c" }}"> + {{ message.update|format_date }} + </time> </p> {% endif %} </div> @@ -193,13 +227,17 @@ {% if upvote_link %} <div class="message-karma"> - {% if user.is_authenticated and helpful_link %} + {% if user.is_authenticated and helpful_link and not is_author %} {% if message.author != user or perms_change %} {% if topic.author = user or perms_change %} <form action="{{ helpful_link }}" method="post"> {% csrf_token %} <button type="submit" class="tick ico-after {% if message.is_useful %}green{% endif %}"> - Cette réponse {% if topic.author == user %}m'{% endif %}a aidé + {% if message.is_useful %} + Cette réponse {% if topic.author == user %}ne m'{% else %}n'{% endif %}a pas aidé + {% else %} + Cette réponse {% if topic.author == user %}m'{% endif %}a aidé + {% endif %} </button> </form> {% endif %} @@ -240,7 +278,7 @@ {% endwith %} {% else %} <span - class="upvote + class="upvote ico-after {% if message.like > message.dislike %}more-voted{% endif %} {% if message.like > 0 %}has-vote{% endif %} @@ -249,10 +287,12 @@ title="{{ message.like }} personnes ont trouvé ce message utile" {% endif %} > - +{{ message.like }} + +<span itemprop="upvoteCount"> + {{ message.like }} + </span> </span> <span - class="downvote + class="downvote ico-after {% if message.like < message.dislike %}more-voted{% endif %} {% if message.dislike > 0 %}has-vote{% endif %} @@ -261,7 +301,9 @@ title="{{ message.dislike }} personnes n'ont pas trouvé ce message utile" {% endif %} > - -{{ message.dislike }} + -<span itemprop="downvoteCount"> + {{ message.dislike }} + </span> </span> {% endif %} {% if upvote_link %} diff --git a/templates/misc/pagination.part.html b/templates/misc/pagination.part.html index 713cf8f4f4..7f4827f8d1 100644 --- a/templates/misc/pagination.part.html +++ b/templates/misc/pagination.part.html @@ -4,11 +4,17 @@ {% if pages|length > 1 %} + {% captureas full_anchor %} + {% if anchor %} + #{{ anchor }} + {% endif %} + {% endcaptureas %} + <ul class="pagination pagination-{{ position }}"> {% ifnotequal nb 1 %} <li class="prev"> {% with prev=nb|add:-1 %} - <a href="{% append_to_get page=prev %}" class="ico-after arrow-left blue"> + <a href="{% append_to_get page=prev %}{{ full_anchor }}" class="ico-after arrow-left blue"> Précédente </a> {% endwith %} @@ -19,7 +25,7 @@ {% for page in pages %} {% if page %} <li> - <a {% ifnotequal page nb %}href="{% append_to_get page=page %}"{% else %}class="current"{% endifnotequal %}> + <a {% ifnotequal page nb %}href="{% append_to_get page=page %}{{ full_anchor }}"{% else %}class="current"{% endifnotequal %}> {{ page }} </a> </li> @@ -49,7 +55,7 @@ {% if pages|last > 0 and pages|last != nb %} <li class="next"> {% with next=nb|add:1 %} - <a href="{% append_to_get page=next %}" class="ico-after arrow-right blue"> + <a href="{% append_to_get page=next %}{{ full_anchor }}" class="ico-after arrow-right blue"> Suivante </a> {% endwith %} diff --git a/templates/misc/validation.part.html b/templates/misc/validation.part.html index de7ea02516..2409529333 100644 --- a/templates/misc/validation.part.html +++ b/templates/misc/validation.part.html @@ -6,7 +6,7 @@ Réservé par {% include "misc/member_item.part.html" with member=validation.validator avatar=True %} {% if validation.date_reserve %} - {{ validation.date_reserve|format_date|capfirst }} + {{ validation.date_reserve|format_date:True|capfirst }} <br> {% endif %} {% endif %} @@ -21,10 +21,10 @@ </button> </form> {% elif validation.is_pending_valid %} - <a href="#unreservation" class="open-modal btn btn-cancel"> + <a href="#unreservation-{{ validation.pk }}" class="open-modal btn btn-cancel"> Annuler la réservation </a> - <form action="{{ reservation_url }}" method="post" class="modal modal-small" id="unreservation"> + <form action="{{ reservation_url }}" method="post" class="modal modal-small" id="unreservation-{{ validation.pk }}"> {% csrf_token %} <p> Actuellement réservé par {% include "misc/member_item.part.html" with member=validation.validator %}. <br> diff --git a/templates/mp/base.html b/templates/mp/base.html index f0b5fa4270..51ae2f2306 100644 --- a/templates/mp/base.html +++ b/templates/mp/base.html @@ -3,7 +3,7 @@ {% block title_base %} - · Messagerie Privée + • Messagerie Privée {% endblock %} diff --git a/templates/mp/index.html b/templates/mp/index.html index 1b2eb2217a..b0639ba756 100644 --- a/templates/mp/index.html +++ b/templates/mp/index.html @@ -38,7 +38,7 @@ {% endspaceless %}</a> <p class="topic-members"> - <span class="topic-members-label">Participants :</span> + <span class="topic-members-label">Participant{{ topic.participants.all|length|add:"1"|pluralize }} :</span> <em> {% include "misc/member_item.part.html" with member=topic.author avatar=True %} </em>{% spaceless %} @@ -51,7 +51,7 @@ {% endwith %} <p class="topic-answers"> {% if topic.get_post_count > 1 %} - {{ topic.get_post_count|add:"-1" }} réponse{% if topic.get_post_count > 2 %}s{% endif %} + {{ topic.get_post_count|add:"-1" }} réponse{{ topic.get_post_count|add:"-1"|pluralize }} {% endif %} </p> <p class="topic-last-answer"> @@ -98,4 +98,4 @@ <h3>Actions</h3> </li> </ul> </div> -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/mp/topic/index.html b/templates/mp/topic/index.html index 9a53fa1187..fcd814c1b3 100644 --- a/templates/mp/topic/index.html +++ b/templates/mp/topic/index.html @@ -32,7 +32,7 @@ {% block content %} <div class="authors"> - <span class="authors-label">Participants : </span> + <span class="authors-label">Participant{{ topic.participants.all|length|add:"1"|pluralize }} : </span> <ul> <li> {% include 'misc/member_item.part.html' with member=topic.author avatar=True %} @@ -127,4 +127,4 @@ <h3>Actions</h3> </li> </ul> </div> -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/pages/about.html b/templates/pages/about.html index 2d28d23629..dd436967c5 100644 --- a/templates/pages/about.html +++ b/templates/pages/about.html @@ -15,7 +15,7 @@ {% block headline %} - <h2>À propos</h2> + <h1>À propos</h1> {% endblock %} diff --git a/templates/pages/alerts.html b/templates/pages/alerts.html index 61baf1819a..6a46544010 100644 --- a/templates/pages/alerts.html +++ b/templates/pages/alerts.html @@ -16,7 +16,7 @@ {% block headline %} - <h2>Liste des alertes en cours</h2> + <h1>Liste des alertes en cours</h1> {% endblock %} diff --git a/templates/pages/assoc_subscribe.html b/templates/pages/assoc_subscribe.html index f6504e3500..ca9685ada5 100644 --- a/templates/pages/assoc_subscribe.html +++ b/templates/pages/assoc_subscribe.html @@ -16,11 +16,40 @@ {% block headline %} - <h2>Adhérer à l'association Zeste de Savoir</h2> + <h1>Adhérer à l'association Zeste de Savoir</h1> {% endblock %} {% block content %} + <h3>Pourquoi adhérer ?</h3> + <p> + Adhérer à l'association Zeste de Savoir permet de nous soutenir de manière concrète et d'officialiser votre + engagement. + </p> + <p> + D'autre part, le financement des activités de l'association, dont bien sûr le site, est intégralement assuré par + les cotisations des membres : adhérer vous permet ainsi d'assurer la pérennité du site. La cotisation, pour + l'année 2014, est de <strong>30 €</strong> (le montant sera révisé tous les ans). Si pour une raison quelconque + vous ne pouvez pas payer cette cotisation, faites-en nous part, nous pouvons accorder des dérogations + exceptionnelles.</p> + <p> + Notez que l'adhésion est subordonnée au paiement de cette cotisation. + </p> + <p> + Adhérer permet bien entendu de participer à l'Assemblée Générale annuelle de l'association, qui élit le Conseil + d'Administration. Si vous avez des idées pour faire avancer l'association et donc le site, c'est le moment ! + </p> + <p> + Une précision importante : <strong>adhérer à l'association ne vous apporte rien sur le site</strong>. L'adhésion + n'est là que pour soutenir le projet, et ne donne droit à aucun pouvoir ou badge supplémentaires. Elle ne donne + même pas accès à un forum secret, puisque le forum de l'association est public ! + </p> + <p> + Enfin, toutes les fonctionnalités du site sont disponibles à tous : aucune n'est conditionnée à l'adhésion à + l'association. + </p> + + <h3>Formulaire d'adhésion</h3> {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/templates/pages/association.html b/templates/pages/association.html index 84257760d6..11368ccb3b 100644 --- a/templates/pages/association.html +++ b/templates/pages/association.html @@ -15,19 +15,43 @@ {% block headline %} - <h2>L'association Zeste de Savoir</h2> + <h1>L'association Zeste de Savoir</h1> {% endblock %} {% block content %} + <h3>Présentation de l'association</h3> <p> - Zeste de Savoir est une association loi 1901 (association à but non lucratif) et joue le rôle de structure légale du site ZesteDeSavoir.com, dont le but est de promouvoir le partage de connaissances à travers des ressources pédagogiques gratuites et de préférence sous licence libre. <a href="http://www.journal-officiel.gouv.fr/publications/assoc/pdf/2014/0016/JOAFE_PDF_Unitaire_20140016_01712.pdf">Parue au Journal officiel le 19 avril 2014</a>, l'association a tenu sa première assemblée générale le 15 mars 2014, au cours de laquelle ont été élus le bureau et le conseil d'administration en présence des <strong>quatorze</strong> membres fondateurs. + Zeste de Savoir est une association loi 1901 (association à but non lucratif) et joue le rôle de structure + légale du site ZesteDeSavoir.com, dont le but est de promouvoir le partage de connaissances à travers des + ressources pédagogiques gratuites et de préférence sous licence libre. + <a href="http://www.journal-officiel.gouv.fr/publications/assoc/pdf/2014/0016/JOAFE_PDF_Unitaire_20140016_01712.pdf">Parue + au Journal officiel le 19 avril 2014</a>, l'association a tenu sa première assemblée générale le 15 mars + 2014, au cours de laquelle ont été élus le bureau et le conseil d'administration en présence des + <strong>quatorze</strong> membres fondateurs. </p> <p> - Ces membres fondateurs sont tous des anciens membres très actifs du feu siteduzero, un site de diffusion de connaissances et d'entraide. À la suite du changement de politique de ce dernier, ils ont décidé de recréer une plateforme sur laquelle ils pourraient à nouveau s'entraider et partager convenablement leurs connaissances et surtout retrouver l'esprit communautaire qu'ils aimaient tant. + Ces membres fondateurs sont tous des anciens membres très actifs du feu siteduzero, un site de diffusion de + connaissances et d'entraide. À la suite du changement de politique de ce dernier, ils ont décidé de recréer une + plateforme sur laquelle ils pourraient à nouveau s'entraider et partager convenablement leurs connaissances et + surtout retrouver l'esprit communautaire qu'ils aimaient tant. </p> <p> Voilà l'histoire de Zeste de Savoir. </p> + + <h3>L'association et le site</h3> + <p> + <strong>Adhérer à l'association ne vous apporte rien sur le site</strong>. L'adhésion n'est là que pour soutenir + le projet, et ne donne droit à aucun pouvoir ou badge supplémentaire. Elle ne donne même pas accès à un forum + secret, puisque le forum de l'association est public ! + </p> + <p> + De même, toutes les fonctionnalités du site sont disponibles à tous : aucune n'est conditionné à l'adhésion à + l'association. + </p> + <p> + Notez que vous devez être inscrit pour pouvoir adhérer. + </p> {% endblock %} \ No newline at end of file diff --git a/templates/pages/contact.html b/templates/pages/contact.html index d874c23cec..5ceb8df23b 100644 --- a/templates/pages/contact.html +++ b/templates/pages/contact.html @@ -15,7 +15,7 @@ {% block headline %} - <h2>Contact</h2> + <h1>Contact</h1> {% endblock %} diff --git a/templates/pages/cookies.html b/templates/pages/cookies.html index f0d5c70394..6d6d6c5cfa 100644 --- a/templates/pages/cookies.html +++ b/templates/pages/cookies.html @@ -15,7 +15,7 @@ {% block headline %} - <h2>À propos des cookies</h2> + <h1>À propos des cookies</h1> {% endblock %} @@ -33,7 +33,7 @@ <h3>À quoi servent les cookies ?</h3> <h4>Cookies techniques</h4> <p>Certaines fonctionnalités du site nécessitent des cookies pour pouvoir être opérationnelles. L'exemple typique est la connexion aux parties privées de Zeste de Savoir.</p> <p>La liste complète des cookies techniques utilisés sur Zeste de Savoir et leur utilité est détaillée un peu plus bas.</p> - <p>Ces cookies, purement techniques, <strong>sont indispensables au foncitonnement de Zeste de Savoir</strong>. Si vous les refusez, les éléments qui en dépendent ne fonctionneront tout simplement pas !</p> + <p>Ces cookies, purement techniques, <strong>sont indispensables au fonctionnement de Zeste de Savoir</strong>. Si vous les refusez, les éléments qui en dépendent ne fonctionneront tout simplement pas !</p> <h4>Cookies d'analyse d'audience</h4> <p>Zeste de Savoir analyse son audience. Que signifie ce terme barbare ? Tout simplement que nous étudions la manière dont le site est utilisé :</p> @@ -111,7 +111,7 @@ <h4>Préparation</h4> <h4>Cuisson</h4> <p>Préchauffez votre four à 220°C / thermostat 7-8, grille en position basse.</p> <p>Façonnez des cookies d'environ 10 cm de côté sur la plaque recouverte de papier sulfurisé. Attention, espacez-les parce qu'ils s'étalent à la cuisson.</p> - <p>Enfournez-lez environ 10 minutes, ils sont cuits lorsqu'ils prennent une belle couleur dorée !</p> + <p>Enfournez-les environ 10 minutes, ils sont cuits lorsqu'ils prennent une belle couleur dorée !</p> <h4>Conseils divers</h4> <p>Si vous avez la flemme de découper le chocolat, vous pouvez utiliser des pépites de chocolat mais en général c'est moins bon. Surtout pas de chocolat à déguster : ces chocolats manquent de sucre pour la patisserie !</p> <p>La cuisson parfaite se joue à quelques secondes près (sérieusement), alors surveillez-les bien ! Un SMS de trop, et vous obtenez des cookies en béton. Ce qui serait dommage !</p> diff --git a/templates/pages/eula.html b/templates/pages/eula.html index c0d599902d..83e84e2ea5 100644 --- a/templates/pages/eula.html +++ b/templates/pages/eula.html @@ -15,7 +15,7 @@ {% block headline %} - <h2>Conditions générales d'utilisation</h2> + <h1>Conditions générales d'utilisation</h1> {% endblock %} diff --git a/templates/tutorial/base.html b/templates/tutorial/base.html index 35cab7c6db..c3280a5f2e 100644 --- a/templates/tutorial/base.html +++ b/templates/tutorial/base.html @@ -4,7 +4,7 @@ {% block title_base %} - · Mes tutoriels + • Tutoriels {% endblock %} @@ -19,7 +19,11 @@ {% if user in tutorial.authors.all %} <li><a href="{% url "zds.member.views.tutorials" %}">Mes tutoriels</a></li> {% else %} - <li><a href="{% url "zds.tutorial.views.index" %}">Tutoriels</a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{% url "zds.tutorial.views.index" %}" itemprop="url"> + <span itemprop="title">Tutoriels</span> + </a> + </li> {% endif %} {% endblock %} @@ -56,28 +60,35 @@ <h3>Télécharger</h3> <ul> {% if tutorial.on_line %} {% if perms.tutorial.change_tutorial %} - <li> - <a href="{% url "zds.tutorial.views.download_markdown" %}?tutoriel={{ tutorial.pk }}" class="ico-after download blue"> - Markdown - </a> - </li> + {% if tutorial.have_markdown %} + <li> + <a href="{% url "zds.tutorial.views.download_markdown" %}?tutoriel={{ tutorial.pk }}" class="ico-after download blue"> + Markdown + </a> + </li> + {% endif %} {% endif %} - + {% if tutorial.have_html %} <li> <a href="{% url "zds.tutorial.views.download_html" %}?tutoriel={{ tutorial.pk }}" class="ico-after download blue"> HTML </a> </li> + {% endif %} + {% if tutorial.have_pdf %} <li> <a href="{% url "zds.tutorial.views.download_pdf" %}?tutoriel={{ tutorial.pk }}" class="ico-after download blue"> PDF </a> </li> + {% endif %} + {% if tutorial.have_epub %} <li> <a href="{% url "zds.tutorial.views.download_epub" %}?tutoriel={{ tutorial.pk }}" class="ico-after download blue"> EPUB </a> </li> + {% endif %} {% endif %} <li> diff --git a/templates/tutorial/base_online.html b/templates/tutorial/base_online.html index 097fdb37df..986c7822bc 100644 --- a/templates/tutorial/base_online.html +++ b/templates/tutorial/base_online.html @@ -4,16 +4,29 @@ {% block title_base %} - · Tutoriels + • Tutoriels {% endblock %} -{% block source_content %} + + +{% block canonical %} {% if tutorial.source and tutorial.source != "" %} - <link rel="canonical" href="{{tutorial.source}}" /> + <link rel="canonical" href="{{ tutorial.source }}"> {% endif %} {% endblock %} + {% block breadcrumb_base %} - <li><a href="{% url "zds.tutorial.views.index" %}">Tutoriels</a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{% url "zds.tutorial.views.index" %}" itemprop="url"> + <span itemprop="title">Tutoriels</span> + </a> + </li> +{% endblock %} + + + +{% block article_schema %} + itemscope itemtype="http://schema.org/Article" {% endblock %} diff --git a/templates/tutorial/chapter/view.html b/templates/tutorial/chapter/view.html index a3e6499ce3..793dc0e561 100644 --- a/templates/tutorial/chapter/view.html +++ b/templates/tutorial/chapter/view.html @@ -21,11 +21,17 @@ {% with authors=tutorial.authors.all %} <h1 {% if chapter.image %}class="illu"{% endif %}> {% if chapter.image %} - <img src="{{ chapter.image.thumb.url }}" alt=""> + <img src="{{ chapter.image.physical.tutorial_illu.url }}" alt=""> {% endif %} {{ chapter.title }} </h1> + {% if tutorial.licence %} + <p class="license"> + {{ tutorial.licence }} + </p> + {% endif %} + {% include 'tutorial/includes/tags_authors.part.html' %} {% if tutorial.in_beta and tutorial.sha_beta == version %} diff --git a/templates/tutorial/chapter/view_online.html b/templates/tutorial/chapter/view_online.html index f131ed15c2..aee1ff44cb 100644 --- a/templates/tutorial/chapter/view_online.html +++ b/templates/tutorial/chapter/view_online.html @@ -10,27 +10,21 @@ {% block breadcrumb %} - <li><a href="{{ chapter.part.tutorial.get_absolute_url_online }}">{{ chapter.part.tutorial.title }}</a></li> - <li><a href="{% url "view-part-url-online" chapter.part.tutorial.pk chapter.part.tutorial.slug chapter.part.pk chapter.part.slug %}"> - {{ chapter.part.title }} - </a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ chapter.part.tutorial.get_absolute_url_online }}" itemprop="url"> + <span itemprop="title">{{ chapter.part.tutorial.title }}</span> + </a> + </li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{% url "view-part-url-online" chapter.part.tutorial.pk chapter.part.tutorial.slug chapter.part.pk chapter.part.slug %}" itemprop="url"> + <span itemprop="title">{{ chapter.part.title }}</span> + </a> + </li> <li>{{ chapter.title }}</li> {% endblock %} -{% block content %} - {% with tutorial=chapter.get_tutorial %} - {% with authors=tutorial.authors.all %} - {% include "tutorial/includes/chapter_pager.part.html" with position="top" online=True %} - {% include "tutorial/includes/chapter_online.part.html" %} - {% include "tutorial/includes/chapter_pager.part.html" with position="bttom" online=True %} - {% endwith %} - {% endwith %} -{% endblock %} - - - {% block headline %} {% if chapter.tutorial %} {% set chapter.tutorial as tutorial %} @@ -45,11 +39,29 @@ <h1 {% if chapter.image %}class="illu"{% endif %}> {{ chapter.title }} </h1> + {% if tutorial.licence %} + <span class="license"> + Licence {{ tutorial.licence }} + </span> + {% endif %} + {% include 'tutorial/includes/tags_authors.part.html' %} {% endblock %} +{% block content %} + {% with tutorial=chapter.get_tutorial %} + {% with authors=tutorial.authors.all %} + {% include "tutorial/includes/chapter_pager.part.html" with position="top" online=True %} + {% include "tutorial/includes/chapter_online.part.html" %} + {% include "tutorial/includes/chapter_pager.part.html" with position="bttom" online=True %} + {% endwith %} + {% endwith %} +{% endblock %} + + + {% block sidebar_blocks %} {% if perms.tutorial.change_tutorial %} <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Administration"> diff --git a/templates/tutorial/comment/edit.html b/templates/tutorial/comment/edit.html index cfd5e822fb..823a2d4e0e 100644 --- a/templates/tutorial/comment/edit.html +++ b/templates/tutorial/comment/edit.html @@ -17,13 +17,15 @@ {% block headline %} - <h2>Éditer le commentaire du tutoriel : {{ tutorial.title }}</h2> + <h1>Éditer le commentaire du tutoriel : {{ tutorial.title }}</h1> {% endblock %} {% block headline_sub %} - <h3 class="subtitle">{{ tutorial.description }}</h3> + <h2 class="subtitle"> + {{ tutorial.description }} + </h2> {% endblock %} diff --git a/templates/tutorial/comment/new.html b/templates/tutorial/comment/new.html index 2158da73c4..307d5dc2b7 100644 --- a/templates/tutorial/comment/new.html +++ b/templates/tutorial/comment/new.html @@ -18,13 +18,13 @@ {% block headline %} - <h2>Ajouter un commentaire au tutoriel : {{tutorial.title}}</h2> + <h1>Ajouter un commentaire au tutoriel : {{ tutorial.title }}</h1> {% endblock %} {% block headline_sub %} - <h3 class="subtitle">{{ tutorial.description }}</h3> + <h2 class="subtitle">{{ tutorial.description }}</h2> {% endblock %} diff --git a/templates/tutorial/extract/edit.html b/templates/tutorial/extract/edit.html index 3969f9c6ef..3f57aeca9f 100644 --- a/templates/tutorial/extract/edit.html +++ b/templates/tutorial/extract/edit.html @@ -23,7 +23,7 @@ {% block headline %} - <h2>Éditer l'extrait</h2> + <h1>Éditer l'extrait</h1> {% endblock %} diff --git a/templates/tutorial/extract/new.html b/templates/tutorial/extract/new.html index b7554199c8..7611988259 100644 --- a/templates/tutorial/extract/new.html +++ b/templates/tutorial/extract/new.html @@ -23,13 +23,15 @@ {% block headline %} - <h2>Nouvel extrait</h2> - - {% if chapter.part %} - <h3 class="subtitle">{{ chapter.title }}</h3> - {% else %} - <h3 class="subtitle">{{ chapter.tutorial.title }}</h3> - {% endif %} + <h1>Nouvel extrait</h1> + + <h2 class="subtitle"> + {% if chapter.part %} + {{ chapter.title }} + {% else %} + {{ chapter.tutorial.title }} + {% endif %} + </h2> {% endblock %} diff --git a/templates/tutorial/includes/chapter.part.html b/templates/tutorial/includes/chapter.part.html index e69036566c..4bc84cdd11 100644 --- a/templates/tutorial/includes/chapter.part.html +++ b/templates/tutorial/includes/chapter.part.html @@ -7,7 +7,7 @@ {% if not chapter.type = 'MINI' %} {% if chapter.intro and chapter.intro != "None" %} {{ chapter.intro|emarkdown }} - {% else %} + {% elif not tutorial.in_beta or tutorial.sha_beta != version %} <p class="ico-after warning"> Il n'y a pas d'introduction. </p> @@ -39,7 +39,7 @@ <h2 id="{{ extract.position_in_chapter }}-{{ extract.title|slugify }}"> </a> </h2> - {% if user in authors %} + {% if user in authors or perms.tutorial.change_tutorial %} <div class="actions-title"> <a href="#delete-{{ extract.pk }}" class="open-modal ico-after cross btn btn-grey">Supprimer</a> <form action="{% url "zds.tutorial.views.modify_extract" %}" method="post" id="delete-{{ extract.pk }}" class="modal modal-small"> @@ -56,7 +56,7 @@ <h2 id="{{ extract.position_in_chapter }}-{{ extract.title|slugify }}"> <a href="#move-extract-{{ extract.pk }}" class="open-modal ico-after move btn btn-grey">Déplacer</a> <form action="{% url "zds.tutorial.views.modify_extract" %}" method="post" class="modal modal-small" id="move-extract-{{ extract.pk }}"> <select name="move_target"> - <option>Déplacer</option> + <option disabled="disabled">Déplacer</option> {% if extract.position_in_chapter > 1 %} <option value="{{ extract.position_in_chapter|add:-1 }}">Monter</option> {% endif %} @@ -88,7 +88,7 @@ <h2 id="{{ extract.position_in_chapter }}-{{ extract.title|slugify }}"> {% csrf_token %} <button type="submit"> - Confirmer + Déplacer </button> </form> @@ -112,7 +112,7 @@ <h2 id="{{ extract.position_in_chapter }}-{{ extract.title|slugify }}"> {% if not chapter.type = 'MINI' %} {% if chapter.conclu and chapter.conclu != "None" %} {{ chapter.conclu|emarkdown }} - {% else %} + {% elif not tutorial.in_beta or tutorial.sha_beta != version %} <p class="ico-after warning"> Il n'y a pas de conclusion. </p> diff --git a/templates/tutorial/includes/chapter_pager.part.html b/templates/tutorial/includes/chapter_pager.part.html index 05f5c71648..953bbcb119 100644 --- a/templates/tutorial/includes/chapter_pager.part.html +++ b/templates/tutorial/includes/chapter_pager.part.html @@ -5,7 +5,7 @@ {% if online %} href="{{ prev.get_absolute_url_online }}" {% else %} - href="{{ prev.get_absolute_url }}{%if version %}?version={{version}}{% endif %}" + href="{{ prev.get_absolute_url }}{% if version %}?version={{ version }}{% endif %}" {% endif %} class="ico-after arrow-left" > @@ -20,7 +20,7 @@ {% if online %} href="{{ next.get_absolute_url_online }}" {% else %} - href="{{ next.get_absolute_url }}{%if version %}?version={{version}}{% endif %}" + href="{{ next.get_absolute_url }}{% if version %}?version={{ version }}{% endif %}" {% endif %} class="ico-after arrow-right" > diff --git a/templates/tutorial/includes/opengraph.part.html b/templates/tutorial/includes/opengraph.part.html new file mode 100644 index 0000000000..72fc3303ad --- /dev/null +++ b/templates/tutorial/includes/opengraph.part.html @@ -0,0 +1,13 @@ +<meta property="og:type" content="article"> + +{% if tutorial.create_at %} + <meta property="og:article:published_time" content="{{ tutorial.create_at|date:"c" }}"> +{% endif %} +{% if tutorial.pubdate %} + <meta property="og:article:modified_time" content="{{ tutorial.pubdate|date:"c" }}"> +{% endif %} + +<meta property="og:article:section" content="Tutoriels"> +{% for catofsubcat in tutorial.subcategory.all %} + <meta property="og:article:tag" content="{{ catofsubcat.title }}"> +{% endfor %} \ No newline at end of file diff --git a/templates/tutorial/includes/part.part.html b/templates/tutorial/includes/part.part.html index 9f563a3d73..9452479d6c 100644 --- a/templates/tutorial/includes/part.part.html +++ b/templates/tutorial/includes/part.part.html @@ -9,7 +9,7 @@ {{ part.intro|safe }} {% endif %} - <ul> + <ol> {% for chapter in chapters %} <li> <h3> @@ -23,7 +23,7 @@ <h3> Chapitre {{ chapter.part.position_in_tutorial }}.{{ chapter.position_in_part }} | {{ chapter.title }} </a> </h3> - <ul> + <ol> {% for extract in chapter.extracts %} <li> <h4> @@ -39,10 +39,10 @@ <h4> </h4> </li> {% endfor %} - </ul> + </ol> </li> {% endfor %} - </ul> + </ol> {% if part.conclu %} {{ part.conclu|safe }} diff --git a/templates/tutorial/includes/summary.part.html b/templates/tutorial/includes/summary.part.html index 52d5ae3359..a7e01d051e 100644 --- a/templates/tutorial/includes/summary.part.html +++ b/templates/tutorial/includes/summary.part.html @@ -10,7 +10,7 @@ <h3>Sommaire</h3> {% if tutorial.type == 'MINI' %} {# Small tutorial #} {% if chapter.extracts %} - <ul> + <ol> {% for extract in chapter.extracts %} <li> <a data-num="{{ extract.position_in_chapter }}" @@ -21,6 +21,12 @@ <h3>Sommaire</h3> </a> </li> {% endfor %} + </ol> + {% else %} + <ul> + <li class="inactive"> + <span class="disabled mobile-menu-link">Aucun extrait actuellement</span> + </li> </ul> {% endif %} {% else %} @@ -29,51 +35,57 @@ <h3>Sommaire</h3> {% for part in tutorial.get_parts %} <h4 data-num="{{ part.position_in_tutorial|roman }}"> <a class="mobile-menu-link" - {% if online %} - href="{% url "view-part-url-online" tutorial.pk tutorial.slug part.pk part.slug %}" - {% else %} - href="{% url "view-part-url" tutorial.pk tutorial.slug part.pk part.slug %}"{% if version %}?version={{ version }}{% endif %} - {% endif %} + {% if online %} + href="{% url "view-part-url-online" tutorial.pk tutorial.slug part.pk part.slug %}" + {% else %} + href="{% url "view-part-url" tutorial.pk tutorial.slug part.pk part.slug %}"{% if version %}?version={{ version }}{% endif %} + {% endif %} > {{ part.title }} </a> </h4> - <ul> + <ol> {% for chapter in part.get_chapters %} <li {% if chapter_current.pk == chapter.pk %}class="current"{% endif %}> <a data-num="{{ chapter.position_in_part }}" - {% if online %} - href="{% url "view-chapter-url-online" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}" - {% else %} - href="{% url "view-chapter-url" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}"{% if version %}?version={{ version }}{% endif %} - {% endif %} - class="mobile-menu-link mobile-menu-sublink {% if chapter_current.pk = chapter.pk %}unread{% endif %}" + {% if online %} + href="{% url "view-chapter-url-online" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}" + {% else %} + href="{% url "view-chapter-url" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}"{% if version %}?version={{ version }}{% endif %} + {% endif %} + class="mobile-menu-link mobile-menu-sublink {% if chapter_current.pk = chapter.pk %}unread{% endif %}" > {{ chapter.title }} </a> {% if chapter_current.pk == chapter.pk %} - <ul class="mobile-menu-bloc mobile-all-links" data-title="Sommaire du chapitre"> + <ol class="mobile-menu-bloc mobile-all-links" data-title="Sommaire du chapitre"> {% for extract in chapter.extracts %} <li> <a - {% if online %} - href="{% url "view-chapter-url-online" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}#{{ extract.position_in_chapter }}-{{ extract.title|slugify }}" - {% else %} - href="{% url "view-chapter-url" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}#{{ extract.position_in_chapter }}-{{ extract.title|slugify }}"{% if version %}?version={{ version }}{% endif %} - {% endif %} + {% if online %} + href="{% url "view-chapter-url-online" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}#{{ extract.position_in_chapter }}-{{ extract.title|slugify }}" + {% else %} + href="{% url "view-chapter-url" tutorial.pk tutorial.slug part.pk part.slug chapter.pk chapter.slug %}#{{ extract.position_in_chapter }}-{{ extract.title|slugify }}"{% if version %}?version={{ version }}{% endif %} + {% endif %} > {{ extract.title }} </a> </li> {% endfor %} - </ul> + </ol> {% endif %} </li> {% endfor %} - </ul> + </ol> {% endfor %} + {% else %} + <ul> + <li class="inactive"> + <span class="disabled mobile-menu-link">Aucune partie actuellement</span> + </li> + </ul> {% endif %} {% endif %} </div> diff --git a/templates/tutorial/includes/tags_authors.part.html b/templates/tutorial/includes/tags_authors.part.html index 625b002dc0..1479ecb3d0 100644 --- a/templates/tutorial/includes/tags_authors.part.html +++ b/templates/tutorial/includes/tags_authors.part.html @@ -1,17 +1,34 @@ +{% load date %} + + + {% if tutorial.subcategory.all|length > 0 %} - <ul class="taglist"> + <ul class="taglist" itemprop="keywords"> {% for catofsubcat in tutorial.subcategory.all %} <li><a href="{{ catofsubcat.get_absolute_url_tutorial }}">{{ catofsubcat.title }}</a></li> {% endfor %} </ul> {% endif %} + + +{% if tutorial.update %} + <span class="pubdate"> + Dernière mise à jour : + <time datetime="{{ tutorial.update|date:"c" }}" pubdate="pubdate" itemprop="dateModified"> + {{ tutorial.update|format_date }} + </time> + </span> +{% endif %} + + + <div class="authors"> - <span class="authors-label">Auteurs : </span> + <span class="authors-label">Auteur{{ tutorial.authors.all|pluralize }} : </span> <ul> {% for member in tutorial.authors.all %} <li> - {% include "misc/member_item.part.html" with avatar=True %} + {% include "misc/member_item.part.html" with avatar=True author=True %} </li> {% endfor %} @@ -23,4 +40,4 @@ </li> {% endif %} </ul> -</div> \ No newline at end of file +</div> diff --git a/templates/tutorial/includes/tutorial_item.part.html b/templates/tutorial/includes/tutorial_item.part.html index e86f4d8933..75d4b06cb1 100644 --- a/templates/tutorial/includes/tutorial_item.part.html +++ b/templates/tutorial/includes/tutorial_item.part.html @@ -8,9 +8,11 @@ {{ tutorial.get_absolute_url_online }} {% endif %} "> - <img src="{{ tutorial.image.physical.tutorial_illu.url }}" alt="" class="tutorial-img avatar"> - <div class="tutorial-infos"> - <h3>{{ tutorial.title }}</h3> + {% if tutorial.image.physical.tutorial_illu.url %} + <img src="{{ tutorial.image.physical.tutorial_illu.url }}" alt="" class="tutorial-img avatar"> + {% endif %} + <div class="tutorial-infos {% if not tutorial.image.physical.tutorial_illu.url %}no-illu{% endif %}"> + <h3 itemprop="itemListElement">{{ tutorial.title }}</h3> <span class="article-metadata"> {% if tutorial.subcategory %} diff --git a/templates/tutorial/index.html b/templates/tutorial/index.html index 92c1743f9a..07ecdfd8b6 100644 --- a/templates/tutorial/index.html +++ b/templates/tutorial/index.html @@ -4,31 +4,49 @@ {% block title %} - Liste des tutoriels + {% if tag %} + {{ tag }} + {% else %} + Tous les tutoriels + {% endif %} {% endblock %} -{% block meta %} - <meta name="description" content="Les tutoriels vous permettent d'apprendre - sur divers sujets tous plus intéressant les uns que les autres." /> +{% block description %} + {% if tag %} + Découvrez tous nos tutoriels sur {{ tag }}. Vous pourrez également découvrir divers sujets tous plus intéressants les uns que les autres. + {% else %} + Les tutoriels vous permettent d'apprendre divers sujets tous plus intéressants + les uns que les autres. + {% endif %} {% endblock %} {% block breadcrumb %} - <li>Liste des tutoriels</li> + {% if tag %} + <li>{{ tag }}</li> + {% else %} + <li>Tous les tutoriels</li> + {% endif %} {% endblock %} {% block content_out %} - <section class="full-content-wrapper"> - <h2 class="ico-after ico-tutorials"> + <section class="full-content-wrapper" itemscope itemtype="http://schema.org/ItemList"> + <h1 class="ico-after ico-tutorials" itemprop="name"> {% block headline %} - Liste des tutoriels + {% if tag %} + Tutoriels : {{ tag }} + {% else %} + Tous les tutoriels + {% endif %} {% endblock %} - </h2> + </h1> + + <meta itemprop="itemListOrder" content="Unordered"> {% if tutorials %} <div class="tutorial-list"> diff --git a/templates/tutorial/member/online.html b/templates/tutorial/member/online.html index dba8b5e450..171035d892 100644 --- a/templates/tutorial/member/online.html +++ b/templates/tutorial/member/online.html @@ -13,12 +13,14 @@ {% block breadcrumb %} {% with profile=usr|profile %} - <li ><a href="{{ profile.get_absolute_url }}">{{ usr.username }}</a></li> + <li><a href="{{ profile.get_absolute_url }}">{{ usr.username }}</a></li> {% endwith %} - <li><a href="{% url "zds.tutorial.views.find_tuto" usr.username %}">Tutoriels publiés</a></li> + <li><a href="{% url "zds.tutorial.views.find_tuto" usr.pk %}">Tutoriels publiés</a></li> <li>Recherche</li> {% endblock %} + + {% block headline %} <h2 class="ico-after ico-tutorials">Tutoriels publiés par {{ usr.username }}</h2> {% endblock %} diff --git a/templates/tutorial/part/view.html b/templates/tutorial/part/view.html index 37c1e8bc92..32f58ed583 100644 --- a/templates/tutorial/part/view.html +++ b/templates/tutorial/part/view.html @@ -21,6 +21,12 @@ <h2> {{ part.title }} </h2> + {% if tutorial.licence %} + <p class="license"> + {{ tutorial.licence }} + </p> + {% endif %} + {% include 'tutorial/includes/tags_authors.part.html' with tutorial=part.tutorial %} {% if part.tutorial.in_beta and part.tutorial.sha_beta == version %} @@ -37,7 +43,7 @@ <h2> {% block content %} {% if part.intro and part.intro != "None" %} {{ part.intro|emarkdown }} - {% else %} + {% elif not tutorial.in_beta or tutorial.sha_beta != version %} <p class="ico-after warning"> Il n'y a pas d'introduction. </p> @@ -84,7 +90,7 @@ <h4> {% if part.conclu and part.conclu != "None" %} {{ part.conclu|emarkdown }} - {% else %} + {% elif not tutorial.in_beta or tutorial.sha_beta != version %} <p class="ico-after warning"> Il n'y a pas de conclusion. </p> @@ -112,6 +118,7 @@ <h4> <a href="#move-part" class="open-modal ico-after move blue"> Déplacer <span class="wide">la partie</span> </a> + <form action="{% url "zds.tutorial.views.modify_part" %}" method="POST" class="modal modal-small" id="move-part"> <select name="move_target" class="select-autosubmit"> <option disabled="disabled"> @@ -168,6 +175,23 @@ <h4> <button type="submit">Déplacer</button> </form> </li> + <li> + <a href="#delete-part" class="open-modal ico-after cross red"> + Supprimer la partie + </a> + + <form action="{% url "zds.tutorial.views.modify_part" %}" method="post" id="delete-part" class="modal modal-medium"> + <p> + Attention, vous vous apprêtez à supprimer définitivement la partie "<em>{{ part.title }}</em>". + </p> + <input type="hidden" name="part" value="{{ part.pk }}"> + + {% csrf_token %} + <button type="submit" name="delete"> + Confirmer + </button> + </form> + </li> {% endif %} {% endif %} {% endblock %} diff --git a/templates/tutorial/part/view_online.html b/templates/tutorial/part/view_online.html index 6928b00120..83690b206a 100644 --- a/templates/tutorial/part/view_online.html +++ b/templates/tutorial/part/view_online.html @@ -8,17 +8,46 @@ +{% block image %}{% spaceless %} + {% if tutorial.image %} + {{ tutorial.image.physical.tutorial_illu.url }} + {% else %} + {{ block.super }} + {% endif %} +{% endspaceless %}{% endblock %} + + + +{% block opengraph %} + {% include "tutorial/includes/opengraph.part.html" %} +{% endblock %} + + + {% block breadcrumb %} - <li><a href="{{ part.tutorial.get_absolute_url_online }}">{{ part.tutorial.title }}</a></li> + <li itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <a href="{{ part.tutorial.get_absolute_url_online }}" itemprop="url"> + <span itemprop="title">{{ part.tutorial.title }}</span> + </a> + </li> <li>{{ part.title }}</li> {% endblock %} {% block headline %} - <h2> + <h1 {% if tutorial.image %}class="illu"{% endif %} itemprop="name"> + {% if tutorial.image %} + <img src="{{ tutorial.image.physical.tutorial_illu.url }}" alt="" itemprop="thumbnailUrl"> + {% endif %} {{ part.title }} - </h2> + </h1> + + {% if tutorial.licence %} + <span class="license" itemprop="license"> + Licence {{ tutorial.licence }} + </span> + {% endif %} {% include 'tutorial/includes/tags_authors.part.html' with tutorial=part.tutorial %} {% endblock %} diff --git a/templates/tutorial/tutorial/history.html b/templates/tutorial/tutorial/history.html index d293c3b675..e92698c1b7 100644 --- a/templates/tutorial/tutorial/history.html +++ b/templates/tutorial/tutorial/history.html @@ -5,90 +5,88 @@ {% block title %} - {{ tutorial.title }} + Historique de "{{ tutorial.title }}" {% endblock %} {% block breadcrumb %} <li><a href="{{ tutorial.get_absolute_url }}">{{ tutorial.title }}</a></li> - <li>Historique</li> + <li>Historique des modifications</li> {% endblock %} {% block headline %} - <h1>{{ tutorial.title }}</h1> - <h3 class="subtitle">{{ tutorial.description }}</h3> + <h1 {% if tutorial.image %}class="illu"{% endif %} itemprop="name"> + {% if tutorial.image %} + <img src="{{ tutorial.image.physical.tutorial_illu.url }}" alt="" itemprop="thumbnailUrl"> + {% endif %} + Historique de "{{ tutorial.title }}" + </h1> + + {% if tutorial.licence %} + <span class="license"> + Licence {{ tutorial.licence }} + </span> + {% endif %} + + <h2 class="subtitle"> + {{ tutorial.description }} + </h2> + + {% include 'tutorial/includes/tags_authors.part.html' %} {% endblock %} {% block content %} - {# TODO : Tout est à refaire ici #} - <div class="row"> - <div class="large-12 columns"> - <div class="large-1 columns hide-for-small"> - {% if tutorial.image %} - <img src="{{tutorial.image.physical.tutorial_illu.url }}" alt=""> - {% endif %} - </div> - <div class="large-11 columns"> - <p> - Tutoriel - {% if not tutorial.is_mini %} - étendu - {% endif %} - rédigé par - {% for member in tutorial.authors.all %} - {% if forloop.counter > 1 %} - {% if forloop.last %} - et + <table class="fullwidth"> + <thead> + <tr> + <th width="10%">Etat</th> + <th width="18%">Date</th> + <th>Version</th> + <th width="10%">Diff.</th> + <th width="15%">Auteur</th> + </tr> + </thead> + <tbody> + {% for log in logs %} + <tr> + <td> + {% if tutorial.sha_validation = log.newhexsha %} + Validation + {% elif tutorial.sha_beta = log.newhexsha %} + Beta + {% elif tutorial.sha_draft = log.newhexsha %} + Draft + {% endif%} + </td> + <td> + {{ log.time.0|humane_time }} + </td> + <td> + <a href="{% url "zds.tutorial.views.view_tutorial" tutorial.pk tutorial.slug %}?version={{ log.newhexsha }}" > + {{ log.message }} + </a> + </td> + <td> + <a href="{% url "zds.tutorial.views.diff" tutorial.pk tutorial.slug %}?sha={{ log.newhexsha }}" > + {{ log.newhexsha|truncatechars:8 }} + </a> + </td> + <td> + {% with u=log.actor.name|user %} + {% if u %} + {% include "misc/member_item.part.html" with member=u avatar=True %} {% else %} - , + <em>Inconnu</em> {% endif %} - {% endif %} - {% include "misc/member_item.part.html" %} - {% endfor %}. - </p> - </div> - </div> - </div> - - <div class="row"> - <div class="large-12 columns"> - <table> - <thead> - <tr> - <th></th> - <th width="25%">Date</th> - <th>Version</th> - <th width="10%">Diff.</th> - <th width="10%">Auteur</th> - </tr> - </thead> - <tbody> - {% for log in logs %} - <tr> - <td><p>{% if tutorial.sha_validation = log.newhexsha %} Validation {% elif tutorial.sha_beta = log.newhexsha %} Beta {% elif tutorial.sha_draft = log.newhexsha %} Draft {% endif%}</p></td> - <td><p>{{ log.time.0|humane_time }}</p></td> - <td><p><a href="{% url "zds.tutorial.views.view_tutorial" tutorial.pk tutorial.slug %}?version={{ log.newhexsha }}" >{{ log.message }}</a></p></td> - <td><p><a href="{% url "zds.tutorial.views.diff" tutorial.pk tutorial.slug %}?sha={{ log.newhexsha }}" >{{ log.newhexsha|truncatechars:8 }}</a></p></td> - <td> - {% with u=log.actor.name|user %} - {% if u %} - {% with profile=u|profile %} - <p><a href="{{ profile.get_absolute_url }}">{{ u.username }}</a></p> - {% endwith %} - {% else %} - <p>Inconnu</p> - {% endif %} - {% endwith %} - </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> - </div> + {% endwith %} + </td> + </tr> + {% endfor %} + </tbody> + </table> {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/tutorial/view.html b/templates/tutorial/tutorial/view.html index c84ea8eeb4..da3f0be57a 100644 --- a/templates/tutorial/tutorial/view.html +++ b/templates/tutorial/tutorial/view.html @@ -25,10 +25,16 @@ <h1 {% if tutorial.image %}class="illu"{% endif %}> {{ tutorial.title }} </h1> + {% if tutorial.licence %} + <p class="license"> + {{ tutorial.licence }} + </p> + {% endif %} + {% if tutorial.description %} - <h3 class="subtitle"> + <h2 class="subtitle"> {{ tutorial.description }} - </h3> + </h2> {% endif %} {% if user in tutorial.authors.all or perms.tutorial.change_tutorial %} @@ -37,6 +43,23 @@ <h3 class="subtitle"> {% include 'tutorial/includes/tags_authors.part.html' with tutorial=tutorial %} {% endif %} + {% if perms.tutorial.change_tutorial and tutorial.sha_validation %} + <p class="content-wrapper alert-box info"> + Ce tutoriel est en attente de validation + </p> + {% if validation.comment_authors %} + <div class="content-wrapper comment-author"> + <p> + Le message suivant a été laissé à destination des validateurs : + </p> + + <blockquote> + {{ validation.comment_authors }} + </blockquote> + </div> + {% endif %} + {% endif %} + {% if tutorial.in_beta and tutorial.sha_beta == version %} <div class="content-wrapper"> <div class="alert-box warning"> @@ -52,7 +75,7 @@ <h3 class="subtitle"> {% with tuto_version=tutorial|repo_tuto:version %} {% if tuto_version.introduction and tuto_version.introduction != "None" %} {{ tuto_version.introduction|emarkdown }} - {% else %} + {% elif not tutorial.in_beta or tutorial.sha_beta != version %} <p class="ico-after warning"> Il n'y a pas d'introduction. </p> @@ -69,7 +92,7 @@ <h3 class="subtitle"> {% for part in parts %} <h2> - <a href="{% url "view-part-url" tutorial.pk tutorial.slug part.pk part.slug %}{%if version %}?version={{version}}{% endif %}"> + <a href="{% url "view-part-url" tutorial.pk tutorial.slug part.pk part.slug %}{% if version %}?version={{ version }}{% endif %}"> Partie {{ part.position_in_tutorial }} : {{ part.title }} </a> </h2> @@ -86,7 +109,7 @@ <h2> {% if tuto_version.conclusion and tuto_version.conclusion != "None" %} {{ tuto_version.conclusion|emarkdown }} - {% else %} + {% elif not tutorial.in_beta or tutorial.sha_beta != version %} <p class="ico-after warning"> Il n'y a pas de conclusion. </p> @@ -246,6 +269,15 @@ <h2> {% crispy formAskValidation %} </div> </li> + {% elif tutorial.sha_validation != version %} + <li> + <a href="#ask-validation" class="open-modal ico-after tick green"> + Mettre à jour la version en validation + </a> + <div id="ask-validation" class="modal modal-medium"> + {% crispy formAskValidation %} + </div> + </li> {% else %} <li class="inactive"> <span class="ico-after tick disabled">En attente de validation</span> @@ -257,7 +289,7 @@ <h2> {% block sidebar_blocks %} - {% if user in tutorial.authors.all or perms.tutorial.change_tutorial %} + {% if perms.tutorial.change_tutorial %} <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Validation"> <h3>Validation</h3> <ul> @@ -298,7 +330,7 @@ <h3>Validation</h3> <form action="{% url "zds.tutorial.views.reservation" validation.pk %}" method="post"> {% csrf_token %} <button type="submit" class="ico-after lock blue"> - Se retirer + Annuler la réservation </button> </form> </li> diff --git a/templates/tutorial/tutorial/view_online.html b/templates/tutorial/tutorial/view_online.html index 9399a9d5b8..12495742e2 100644 --- a/templates/tutorial/tutorial/view_online.html +++ b/templates/tutorial/tutorial/view_online.html @@ -13,6 +13,28 @@ +{% block description %} + {{ tutorial.description }} +{% endblock %} + + + +{% block image %}{% spaceless %} + {% if tutorial.image %} + {{ tutorial.image.physical.tutorial_illu.url }} + {% else %} + {{ block.super }} + {% endif %} +{% endspaceless %}{% endblock %} + + + +{% block opengraph %} + {% include "tutorial/includes/opengraph.part.html" %} +{% endblock %} + + + {% block breadcrumb %} <li>{{ tutorial.title }}</li> {% endblock %} @@ -20,20 +42,26 @@ {% block headline %} - <h1 {% if tutorial.image %}class="illu"{% endif %}> + <h1 {% if tutorial.image %}class="illu"{% endif %} itemprop="name"> {% if tutorial.image %} - <img src="{{ tutorial.image.physical.tutorial_illu.url }}" alt=""> + <img src="{{ tutorial.image.physical.tutorial_illu.url }}" alt="" itemprop="thumbnailUrl"> {% endif %} {{ tutorial.title }} </h1> + {% if tutorial.licence %} + <span class="license" itemprop="license"> + Licence {{ tutorial.licence }} + </span> + {% endif %} + {% if tutorial.description %} - <h3 class="subtitle"> + <h2 class="subtitle" itemprop="description"> {{ tutorial.description }} - </h3> + </h2> {% endif %} - {% include 'tutorial/includes/tags_authors.part.html' with tutorial=tutorial %} + {% include "tutorial/includes/tags_authors.part.html" with tutorial=tutorial %} {% endblock %} @@ -76,10 +104,19 @@ <h2> {% block content_after %} - <h2 class="reactions-title">Commentaires</h2> + <h3 class="comments-title" id="comments"> + {% if tutorial.get_note_count > 0 %} + <span itemprop="commentCount"> + {{ tutorial.get_note_count }} + </span> + commentaire{{ tutorial.get_note_count|pluralize }} + {% else %} + Aucun commentaire + {% endif %} + </h3> - {% include "misc/pagination.part.html" with position="top" topic=tutorial is_online=True %} + {% include "misc/pagination.part.html" with position="top" topic=tutorial is_online=True anchor="comments" %} {% for message in notes %} @@ -110,11 +147,11 @@ <h2 class="reactions-title">Commentaires</h2> {% endif %} - {% include "misc/message.part.html" with perms_change=perms.tutorial.change_note topic=tutorial %} + {% include "misc/message.part.html" with perms_change=perms.tutorial.change_note topic=tutorial comment_schema=True %} {% endfor %} - {% include "misc/pagination.part.html" with position="bottom" topic=tutorial is_online=True %} + {% include "misc/pagination.part.html" with position="bottom" topic=tutorial is_online=True anchor="comments" %} diff --git a/templates/tutorial/validation/history.html b/templates/tutorial/validation/history.html index a5ddb5e686..2c88442295 100644 --- a/templates/tutorial/validation/history.html +++ b/templates/tutorial/validation/history.html @@ -1,5 +1,6 @@ {% extends "tutorial/validation/index.html" %} {% load date %} +{% load captureas %} @@ -46,7 +47,10 @@ {{ validation.date_proposition|format_date|capfirst }} </td> <td> - {% include "tutorial/includes/validation.part.html" %} + {% captureas reservation_url %} + {% url "zds.tutorial.views.reservation" validation.pk %} + {% endcaptureas %} + {% include "misc/validation.part.html" %} </td> </tr> {% endfor %} diff --git a/templates/tutorial/validation/index.html b/templates/tutorial/validation/index.html index 631556b39a..995322b288 100644 --- a/templates/tutorial/validation/index.html +++ b/templates/tutorial/validation/index.html @@ -6,6 +6,11 @@ {% block title %} Validation + {% if request.GET.type == "reserved" %} + / Reservés + {% elif request.GET.type == "orphan" %} + / Non-reservés + {% endif %} {% endblock %} @@ -18,22 +23,27 @@ {% block content_out %} <section class="full-content-wrapper"> - <h2> + <h1> {% block headline %} Validation des tutoriels + {% if request.GET.type == "reserved" %} + / Reservés + {% elif request.GET.type == "orphan" %} + / Non-reservés + {% endif %} {% endblock %} - </h2> + </h1> {% captureas headlinesub %} {% block headline_sub %}{% endblock %} {% endcaptureas %} {% if headlinesub %} - <h3 class="subtitle">{{ headlinesub|safe }}</h3> + <h2 class="subtitle">{{ headlinesub|safe }}</h2> {% endif %} {% block content %} {% if validations %} - <table> + <table class="fullwidth"> <thead> <tr> <th>Titre</th> @@ -89,22 +99,26 @@ <h3 class="subtitle">{{ headlinesub|safe }}</h3> {% block sidebar_new %}{% endblock %} {% block sidebar_blocks %} - <h3>Filtres</h3> - <ul> - <li> - <a href="{% url "zds.tutorial.views.list_validation" %}?type=reserved" class="ico-after tick green"> - En cours de validation - </a> - </li> - <li> - <a href="{% url "zds.tutorial.views.list_validation" %}?type=orphan" class="ico-after tick"> - En attente de validateur - </a> - </li> - <li> - <a href="{% url "zds.tutorial.views.list_validation" %}" class="ico-after cross blue"> - Annuler le filtre - </a> - </li> - </ul> + <div class="mobile-menu-bloc mobile-all-links mobile-show-ico" data-title="Filtres"> + <h3>Filtres</h3> + <ul> + <li> + <a href="{% url "zds.tutorial.views.list_validation" %}?type=reserved" class="ico-after tick green {% if request.GET.type == "reserved" %}unread{% endif %}"> + En cours de validation + </a> + </li> + <li> + <a href="{% url "zds.tutorial.views.list_validation" %}?type=orphan" class="ico-after tick {% if request.GET.type == "orphan" %}unread{% endif %}"> + En attente de validateur + </a> + </li> + {% if request.GET.type %} + <li> + <a href="{% url "zds.tutorial.views.list_validation" %}" class="ico-after cross blue"> + Annuler le filtre + </a> + </li> + {% endif %} + </ul> + </div> {% endblock %} \ No newline at end of file diff --git a/zds/article/factories.py b/zds/article/factories.py index 033ae18988..bc05157a9b 100644 --- a/zds/article/factories.py +++ b/zds/article/factories.py @@ -14,7 +14,7 @@ import json as json_writer import os from zds.article.models import Article, Reaction, \ - Validation + Validation, Licence from zds.utils.articles import export_article @@ -70,3 +70,15 @@ def _prepare(cls, create, **kwargs): class VaidationFactory(factory.DjangoModelFactory): FACTORY_FOR = Validation + +class LicenceFactory(factory.DjangoModelFactory): + FACTORY_FOR = Licence + + code = u'GNU_GPL' + title = u'GNU General Public License' + + @classmethod + def _prepare(cls, create, **kwargs): + licence = super(LicenceFactory, cls)._prepare(create, **kwargs) + return licence + diff --git a/zds/article/feeds.py b/zds/article/feeds.py index caf39f15be..da64ad749b 100644 --- a/zds/article/feeds.py +++ b/zds/article/feeds.py @@ -14,20 +14,25 @@ class LastArticlesFeedRSS(Feed): def items(self): return Article.objects\ - .filter(is_visible=True)\ + .filter(sha_public__isnull=False)\ .order_by('-pubdate')[:5] def item_title(self, item): return item.title + def item_pubdate(self, item): + return item.pubdate + def item_description(self, item): return item.description def item_author_name(self, item): - return item.author.username - - def item_author_link(self, item): - return item.author.get_absolute_url() + authors_list = item.authors.all() + authors = [] + for authors_obj in authors_list: + authors.append(authors_obj.username) + authors = ", ".join(authors) + return authors def item_link(self, item): return item.get_absolute_url() diff --git a/zds/article/forms.py b/zds/article/forms.py index 64999bb7b2..eb55728cbf 100644 --- a/zds/article/forms.py +++ b/zds/article/forms.py @@ -9,7 +9,7 @@ from zds.article.models import Article from zds.utils.forms import CommonLayoutEditor -from zds.utils.models import SubCategory +from zds.utils.models import SubCategory, Licence class ArticleForm(forms.Form): @@ -52,6 +52,12 @@ class ArticleForm(forms.Form): queryset=SubCategory.objects.all(), required=False ) + + licence = forms.ModelChoiceField( + label="Licence de votre publication", + queryset=Licence.objects.all(), + required=False, + ) def __init__(self, *args, **kwargs): super(ArticleForm, self).__init__(*args, **kwargs) @@ -64,6 +70,7 @@ def __init__(self, *args, **kwargs): Field('description', autocomplete='off'), Field('image'), Field('subcategory'), + Field('licence'), CommonLayoutEditor(), ) diff --git a/zds/article/migrations/0003_auto__del_field_article_thumbnail__add_field_article_licence.py b/zds/article/migrations/0003_auto__del_field_article_thumbnail__add_field_article_licence.py new file mode 100644 index 0000000000..277510012a --- /dev/null +++ b/zds/article/migrations/0003_auto__del_field_article_thumbnail__add_field_article_licence.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'Article.thumbnail' + db.delete_column(u'article_article', 'thumbnail') + + # Adding field 'Article.licence' + db.add_column(u'article_article', 'licence', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['utils.Licence'], null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'Article.thumbnail' + db.add_column(u'article_article', 'thumbnail', + self.gf('django.db.models.fields.files.ImageField')(max_length=100, null=True, blank=True), + keep_default=False) + + # Deleting field 'Article.licence' + db.delete_column(u'article_article', 'licence_id') + + + models = { + u'article.article': { + 'Meta': {'object_name': 'Article'}, + 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'}), + 'create_at': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'is_locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'last_reaction': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_reaction'", 'null': 'True', 'to': u"orm['article.Reaction']"}), + 'licence': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['utils.Licence']", 'null': 'True', 'blank': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), + 'sha_draft': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_public': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'sha_validation': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subcategory': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['utils.SubCategory']", 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'article.articleread': { + 'Meta': {'object_name': 'ArticleRead'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Article']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reaction': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Reaction']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reactions_read'", 'to': u"orm['auth.User']"}) + }, + u'article.reaction': { + 'Meta': {'object_name': 'Reaction', '_ormbases': [u'utils.Comment']}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Article']"}), + u'comment_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['utils.Comment']", 'unique': 'True', 'primary_key': 'True'}) + }, + u'article.validation': { + 'Meta': {'object_name': 'Validation'}, + 'article': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['article.Article']", 'null': 'True', 'blank': 'True'}), + 'comment_authors': ('django.db.models.fields.TextField', [], {}), + 'comment_validator': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_proposition': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'date_reserve': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'date_validation': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '10'}), + 'validator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'articles_author_validations'", 'null': 'True', 'to': u"orm['auth.User']"}), + 'version': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}) + }, + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'utils.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': u"orm['auth.User']"}), + 'dislike': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'editor': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments-editor'", 'null': 'True', 'to': u"orm['auth.User']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.CharField', [], {'max_length': '39'}), + 'is_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'like': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'position': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'pubdate': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'text_hidden': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80'}), + 'text_html': ('django.db.models.fields.TextField', [], {}), + 'update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + u'utils.licence': { + 'Meta': {'object_name': 'Licence'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'description': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + }, + u'utils.subcategory': { + 'Meta': {'object_name': 'SubCategory'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '80'}), + 'subtitle': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '80'}) + } + } + + complete_apps = ['article'] \ No newline at end of file diff --git a/zds/article/models.py b/zds/article/models.py index 481492fe1a..319afe9f34 100644 --- a/zds/article/models.py +++ b/zds/article/models.py @@ -26,7 +26,7 @@ from zds.utils import get_current_user from zds.utils import slugify from zds.utils.articles import export_article -from zds.utils.models import SubCategory, Comment +from zds.utils.models import SubCategory, Comment, Licence from django.core.urlresolvers import reverse @@ -88,6 +88,10 @@ class Meta: verbose_name='Derniere réaction') is_locked = models.BooleanField('Est verrouillé', default=False) + licence = models.ForeignKey(Licence, + verbose_name='Licence', + blank=True, null=True, db_index=True) + def __unicode__(self): return self.title diff --git a/zds/article/tests.py b/zds/article/tests.py index a826801bb7..40abce441e 100644 --- a/zds/article/tests.py +++ b/zds/article/tests.py @@ -8,8 +8,9 @@ from django.test import TestCase from django.test.utils import override_settings -from zds.article.factories import ArticleFactory, ReactionFactory -from zds.article.models import Validation, Reaction, Article +from zds.article.factories import ArticleFactory, ReactionFactory, \ + LicenceFactory +from zds.article.models import Validation, Reaction, Article, Licence from zds.member.factories import ProfileFactory, StaffProfileFactory from zds.mp.models import PrivateTopic from zds.settings import SITE_ROOT @@ -33,9 +34,12 @@ def setUp(self): self.user_author = ProfileFactory().user self.user = ProfileFactory().user self.staff = StaffProfileFactory().user + + self.licence = LicenceFactory() self.article = ArticleFactory() self.article.authors.add(self.user_author) + self.article.licence = self.licence self.article.save() # connect with user @@ -371,6 +375,134 @@ def test_url_for_staff(self): follow=True) self.assertEqual(result.status_code, 200) + def test_workflow_licence(self): + '''Ensure the behavior of licence on articles''' + + # create a new licence + new_licence = LicenceFactory(code='CC_BY', title='Creative Commons BY') + new_licence = Licence.objects.get(pk=new_licence.pk) + + # check value first + article = Article.objects.get(pk=self.article.pk) + self.assertEqual(article.licence.pk, self.licence.pk) + + # logout before + self.client.logout() + # login with author + self.assertTrue( + self.client.login( + username=self.user_author.username, + password='hostel77') + ) + + # change licence (get 302) : + result = self.client.post( + reverse('zds.article.views.edit') + + '?article={}'.format(self.article.pk), + { + 'title': self.article.title, + 'description': self.article.description, + 'text': self.article.get_text(), + 'subcategory': self.article.subcategory.all(), + 'licence' : new_licence.pk + }, + follow=False) + self.assertEqual(result.status_code, 302) + + # test change : + article = Article.objects.get(pk=self.article.pk) + self.assertNotEqual(article.licence.pk, self.licence.pk) + self.assertEqual(article.licence.pk, new_licence.pk) + + # test change in JSON : + json = article.load_json() + self.assertEquals(json['licence'], new_licence.code) + + # then logout ... + self.client.logout() + # ... and login with staff + self.assertTrue( + self.client.login( + username=self.staff.username, + password='hostel77') + ) + + # change licence back to old one (get 302, staff can change licence) : + result = self.client.post( + reverse('zds.article.views.edit') + + '?article={}'.format(self.article.pk), + { + 'title': self.article.title, + 'description': self.article.description, + 'text': self.article.get_text(), + 'subcategory': self.article.subcategory.all(), + 'licence' : self.licence.pk + }, + follow=False) + self.assertEqual(result.status_code, 302) + + # test change : + article = Article.objects.get(pk=self.article.pk) + self.assertEqual(article.licence.pk, self.licence.pk) + self.assertNotEqual(article.licence.pk, new_licence.pk) + + # test change in JSON : + json = article.load_json() + self.assertEquals(json['licence'], self.licence.code) + + # then logout ... + self.client.logout() + + # change licence (get 302, redirection to login page) : + result = self.client.post( + reverse('zds.article.views.edit') + + '?article={}'.format(self.article.pk), + { + 'title': self.article.title, + 'description': self.article.description, + 'text': self.article.get_text(), + 'subcategory': self.article.subcategory.all(), + 'licence' : new_licence.pk + }, + follow=False) + self.assertEqual(result.status_code, 302) + + # test change (normaly, nothing has) : + article = Article.objects.get(pk=self.article.pk) + self.assertEqual(article.licence.pk, self.licence.pk) + self.assertNotEqual(article.licence.pk, new_licence.pk) + + # login with random user + self.assertTrue( + self.client.login( + username=self.user.username, + password='hostel77') + ) + + # change licence (get 403, random user cannot edit article if not in + # authors list) : + result = self.client.post( + reverse('zds.article.views.edit') + + '?article={}'.format(self.article.pk), + { + 'title': self.article.title, + 'description': self.article.description, + 'text': self.article.get_text(), + 'subcategory': self.article.subcategory.all(), + 'licence' : new_licence.pk + }, + follow=False) + self.assertEqual(result.status_code, 403) + + # test change (normaly, nothing has) : + article = Article.objects.get(pk=self.article.pk) + self.assertEqual(article.licence.pk, self.licence.pk) + self.assertNotEqual(article.licence.pk, new_licence.pk) + + # test change in JSON (normaly, nothing has) : + json = article.load_json() + self.assertEquals(json['licence'], self.licence.code) + def tearDown(self): if os.path.isdir(settings.REPO_ARTICLE_PATH): shutil.rmtree(settings.REPO_ARTICLE_PATH) diff --git a/zds/article/urls.py b/zds/article/urls.py index 12f4f9d2af..f1b2532f12 100644 --- a/zds/article/urls.py +++ b/zds/article/urls.py @@ -29,7 +29,7 @@ url(r'^nouveau/$', 'zds.article.views.new'), url(r'^editer/$', 'zds.article.views.edit'), url(r'^modifier/$', 'zds.article.views.modify'), - url(r'^recherche/(?P<name>.+)/$', + url(r'^recherche/(?P<name>\d+)/$', 'zds.article.views.find_article'), diff --git a/zds/article/views.py b/zds/article/views.py index 7d82f6d7cf..b08d78a184 100644 --- a/zds/article/views.py +++ b/zds/article/views.py @@ -36,7 +36,7 @@ from zds.utils.articles import * from zds.utils.mps import send_mp from zds.utils.models import SubCategory, Category, CommentLike, \ - CommentDislike, Alert + CommentDislike, Alert, Licence from zds.utils.paginator import paginator_range from zds.utils.templatetags.emarkdown import emarkdown @@ -69,6 +69,7 @@ def index(request): return render_template('article/index.html', { 'articles': article, + 'tag': tag, }) @@ -110,6 +111,7 @@ def view(request, article_pk, article_slug): article_version['pk'] = article.pk article_version['slug'] = article.slug article_version['image'] = article.image + article_version['pubdate'] = article.pubdate article_version['sha_draft'] = article.sha_draft article_version['sha_validation'] = article.sha_validation article_version['sha_public'] = article.sha_public @@ -150,7 +152,9 @@ def view_online(request, article_pk, article_slug): article_version['pk'] = article.pk article_version['slug'] = article.slug article_version['image'] = article.image + article_version['pubdate'] = article.pubdate article_version['is_locked'] = article.is_locked + article_version['get_reaction_count'] = article.get_reaction_count article_version['get_absolute_url'] = article.get_absolute_url() article_version['get_absolute_url_online'] = article.get_absolute_url_online() @@ -232,9 +236,10 @@ def new(request): 'description': request.POST['description'], 'text': request.POST['text'], 'image': image, - 'subcategory': request.POST.getlist('subcategory') + 'subcategory': request.POST.getlist('subcategory'), + 'licence': request.POST['licence'] }) - return render_template('article/new.html', { + return render_template('article/member/new.html', { 'text': request.POST['text'], 'form': form }) @@ -266,6 +271,11 @@ def new(request): for subcat in form.cleaned_data['subcategory']: article.subcategory.add(subcat) + # add a licence to the article + if "licence" in data and data["licence"] != "": + lc = Licence.objects.filter(pk=data["licence"]).all()[0] + article.licence = lc + article.save() maj_repo_article(request, @@ -314,6 +324,14 @@ def edit(request): for subcat in form.cleaned_data['subcategory']: article.subcategory.add(subcat) + if "licence" in data: + if data["licence"] != "": + lc = Licence.objects.filter(pk=data["licence"]).all()[0] + article.licence = lc + else: + article.licence = None + + article.save() new_slug = os.path.join( @@ -329,11 +347,16 @@ def edit(request): return redirect(article.get_absolute_url()) else: + if "licence" in json: + licence = Licence.objects.filter(code=json["licence"]).all()[0] + else: + licence = None form = ArticleForm(initial={ 'title': json['title'], 'description': json['description'], 'text': article.get_text(), 'subcategory': article.subcategory.all(), + 'licence' : licence }) return render_template('article/member/edit.html', { @@ -366,8 +389,9 @@ def maj_repo_article( shutil.rmtree(old_slug_path) else: if action == 'maj': - shutil.move(old_slug_path, new_slug_path) - repo = Repo(new_slug_path) + if old_slug_path != new_slug_path: + shutil.move(old_slug_path, new_slug_path) + repo = Repo(new_slug_path) msg = 'Modification de l\'article' elif action == 'add': os.makedirs(new_slug_path, mode=0o777) @@ -543,15 +567,16 @@ def modify(request): # send feedback for author in article.authors.all(): - msg = u'Félicitations **{0}** ! Ton zeste [{1}]({2}) ' - u'est maintenant publié ! Les lecteurs du monde entier ' - u'peuvent venir le lire et réagir a son sujet. Je te conseille ' - u'de rester a leur écoute afin d\'apporter des ' - u'corrections/compléments. Un Article vivant et a jour ' - u'est bien plus lu qu\'un sujet abandonné !'.format( - author.username, - article.title, - article.get_absolute_url_online()) + msg = ( + u'Félicitations **{0}** ! Ton zeste [{1}]({2}) ' + u'est maintenant publié ! Les lecteurs du monde entier ' + u'peuvent venir le lire et réagir à son sujet. Je te conseille ' + u'de rester à leur écoute afin d\'apporter des ' + u'corrections/compléments. Un article vivant et à jour ' + u'est bien plus lu qu\'un sujet abandonné !' + .format(author.username, + article.title, + settings.SITE_URL + article.get_absolute_url_online())) bot = get_object_or_404(User, username=settings.BOT_ACCOUNT) send_mp( bot, @@ -745,6 +770,8 @@ def history_validation(request, article_pk): return render_template('article/validation/history.html', { 'validations': validations, 'article': article, + 'authors': article.authors, + 'tags': article.subcategory, }) @@ -777,7 +804,7 @@ def history(request, article_pk, article_slug): """Display an article.""" article = get_object_or_404(Article, pk=article_pk) - if not article.on_line \ + if not article.on_line() \ and not request.user.has_perm('article.change_article') \ and request.user not in article.authors.all(): raise Http404 @@ -907,9 +934,10 @@ def answer(request): for line in reaction_cite.text.splitlines(): text = text + '> ' + line + '\n' - text = u'{0}Source:[{1}]({2})'.format( + text = u'{0}Source:[{1}]({2}{3})'.format( text, reaction_cite.author.username, + settings.SITE_URL, reaction_cite.get_absolute_url()) form = ReactionForm(article, request.user, initial={ @@ -935,7 +963,7 @@ def solve_alert(request): alert = get_object_or_404(Alert, pk=request.POST['alert_pk']) reaction = Reaction.objects.get(pk=alert.comment.id) bot = get_object_or_404(User, username=settings.BOT_ACCOUNT) - msg = u'Bonjour {0},\n\nVous recevez ce message car vous avez ' + msg = (u'Bonjour {0},\n\nVous recevez ce message car vous avez ' u'signalé le message de *{1}*, dans l\'article [{2}]({3}). ' u'Votre alerte a été traitée par **{4}** et il vous a laissé ' u'le message suivant :\n\n`{5}`\n\n\nToute l\'équipe de ' @@ -946,7 +974,7 @@ def solve_alert(request): settings.SITE_URL + reaction.get_absolute_url(), request.user.username, - request.POST['text']) + request.POST['text'])) send_mp( bot, [ alert.author], u"Résolution d'alerte : {0}".format( diff --git a/zds/forum/feeds.py b/zds/forum/feeds.py index f415a0274e..e3fda2ddeb 100644 --- a/zds/forum/feeds.py +++ b/zds/forum/feeds.py @@ -12,8 +12,8 @@ class LastPostsFeedRSS(Feed): title = u'Derniers messages sur Zeste de Savoir' link = '/forums/' - description = u'Les derniers messages ' - u'parus sur le forum de Zeste de Savoir.' + description = (u'Les derniers messages ' + u'parus sur le forum de Zeste de Savoir.') def items(self): posts = Post.objects.filter(topic__forum__group__isnull=True)\ @@ -23,6 +23,9 @@ def items(self): def item_title(self, item): return u'{}, message #{}'.format(item.topic.title, item.pk) + def item_pubdate(self, item): + return item.pubdate + def item_description(self, item): # TODO: Use cached Markdown when implemented return emarkdown(item.text) @@ -51,6 +54,8 @@ def items(self): topics = Topic.objects.filter(forum__group__isnull=True)\ .order_by('-pubdate') return topics[:5] + def item_pubdate(self, item): + return item.pubdate def item_title(self, item): return u'{} dans {}'.format(item.title, item.forum.title) diff --git a/zds/forum/forms.py b/zds/forum/forms.py index c5f5958916..a0274dfc78 100644 --- a/zds/forum/forms.py +++ b/zds/forum/forms.py @@ -19,6 +19,7 @@ class TopicForm(forms.Form): max_length=Topic._meta.get_field('title').max_length, widget=forms.TextInput( attrs={ + 'placeholder': '[Tag 1][Tag 2] Titre de mon sujet', 'required': 'required', } ) @@ -35,7 +36,7 @@ class TopicForm(forms.Form): widget=forms.Textarea( attrs={ 'placeholder': 'Votre message au format Markdown.', - 'required': 'required' + 'required': 'required', } ) ) @@ -102,7 +103,7 @@ class PostForm(forms.Form): widget=forms.Textarea( attrs={ 'placeholder': 'Votre message au format Markdown.', - 'required': 'required' + 'required': 'required', } ) ) @@ -125,11 +126,12 @@ def __init__(self, topic, user, *args, **kwargs): u'afin de limiter le flood.', disabled=True) elif topic.is_locked: - self.helper['text'].wrap( - Field, - placeholder=u'Ce topic est verrouillé.', - disabled=True - ) + if 'text' not in self.initial: + self.helper['text'].wrap( + Field, + placeholder=u'Ce topic est verrouillé.', + disabled=True + ) def clean(self): cleaned_data = super(PostForm, self).clean() diff --git a/zds/forum/models.py b/zds/forum/models.py index 5e695162cb..654289cdf3 100644 --- a/zds/forum/models.py +++ b/zds/forum/models.py @@ -243,11 +243,8 @@ def is_followed(self, user=None): if user is None: user = get_current_user() - try: - TopicFollowed.objects.get(topic=self, user=user) - except TopicFollowed.DoesNotExist: - return False - return True + return TopicFollowed.objects.filter(topic=self, user=user).exists() + def is_email_followed(self, user=None): """Check if the topic is currently email followed by the user. diff --git a/zds/forum/urls.py b/zds/forum/urls.py index c55967a2b8..909f1467f8 100644 --- a/zds/forum/urls.py +++ b/zds/forum/urls.py @@ -46,7 +46,7 @@ 'zds.forum.views.move_topic'), url(r'^sujet/(?P<topic_pk>\d+)/(?P<topic_slug>.+)/$', 'zds.forum.views.topic'), - url(r'^sujets/membre/(?P<user_pk>.+)/$', + url(r'^sujets/membre/(?P<user_pk>\d+)/$', 'zds.forum.views.find_topic'), url(r'^sujets/tag/(?P<tag_pk>\d+)/(?P<tag_slug>.+)/$', 'zds.forum.views.find_topic_by_tag'), @@ -66,7 +66,7 @@ 'zds.forum.views.like_post'), url(r'^message/dislike/$', 'zds.forum.views.dislike_post'), - url(r'^messages/(?P<user_pk>.+)/$', + url(r'^messages/(?P<user_pk>\d+)/$', 'zds.forum.views.find_post'), # Forum details diff --git a/zds/forum/views.py b/zds/forum/views.py index 148795486f..271e742772 100644 --- a/zds/forum/views.py +++ b/zds/forum/views.py @@ -109,14 +109,17 @@ def cat_details(request, cat_slug): category = get_object_or_404(Category, slug=cat_slug) - forums_pub = Forum.objects.filter(group__isnull=True).select_related("category").all() + forums_pub = Forum.objects\ + .filter(group__isnull=True, category__pk=category.pk)\ + .select_related("category").all() if request.user.is_authenticated(): - forums_prv = Forum.objects.filter(group__isnull=False).select_related("category").all() - out = [] - for forum in forums_prv: - if forum.can_read(request.user): - out.append(forum.pk) - forums = forums_pub|forums_prv.exclude(pk__in=out) + forums_prv = Forum.objects\ + .filter(group__isnull=False, \ + group__in=request.user.groups.all(), \ + category__pk=category.pk)\ + .select_related("category")\ + .all() + forums = forums_pub|forums_prv else : forums = forums_pub @@ -876,6 +879,7 @@ def find_topic_by_tag(request, tag_pk, tag_slug): tag = Tag.objects.filter(pk=tag_pk, slug=tag_slug).first() if tag is None: return redirect(reverse("zds.forum.views.index")) + u = request.user if "filter" in request.GET: filter = request.GET["filter"] if request.GET["filter"] == "solve": @@ -930,16 +934,15 @@ def find_topic_by_tag(request, tag_pk, tag_slug): def find_topic(request, user_pk): """Finds all topics of a user.""" - u = get_object_or_404(User, pk=user_pk) + displayed_user = get_object_or_404(User, pk=user_pk) topics = \ Topic.objects\ - .filter(author=u)\ - .exclude(Q(forum__group__isnull=False) & ~Q(forum__group__in=u.groups.all()))\ + .filter(author=displayed_user)\ + .exclude(Q(forum__group__isnull=False) & ~Q(forum__group__in=request.user.groups.all()))\ .prefetch_related("author")\ .order_by("-pubdate").all() # Paginator - paginator = Paginator(topics, settings.TOPICS_PER_PAGE) page = request.GET.get("page") try: @@ -954,34 +957,32 @@ def find_topic(request, user_pk): return render_template("forum/find/topic.html", { "topics": shown_topics, - "usr": u, + "usr": displayed_user, "pages": paginator_range(page, paginator.num_pages), "nb": page, }) - def find_post(request, user_pk): """Finds all posts of a user.""" - u = get_object_or_404(User, pk=user_pk) + displayed_user = get_object_or_404(User, pk=user_pk) + user = request.user - if request.user.has_perm("forum.change_post"): + if user.has_perm("forum.change_post"): posts = \ - Post.objects.filter(author=u)\ - .exclude(Q(topic__forum__group__isnull=False) & ~Q(topic__forum__group__in=u.groups.all()))\ + Post.objects.filter(author=displayed_user)\ + .exclude(Q(topic__forum__group__isnull=False) & ~Q(topic__forum__group__in=user.groups.all()))\ .prefetch_related("author")\ .order_by("-pubdate").all() else: posts = \ - Post.objects.filter(author=u)\ + Post.objects.filter(author=displayed_user)\ .filter(is_visible=True)\ - .exclude(Q(topic__forum__group__isnull=False) & ~Q(topic__forum__group__in=u.groups.all()))\ + .exclude(Q(topic__forum__group__isnull=False) & ~Q(topic__forum__group__in=user.groups.all()))\ .prefetch_related("author").order_by("-pubdate").all() - # Paginator - paginator = Paginator(posts, settings.POSTS_PER_PAGE) page = request.GET.get("page") try: @@ -996,14 +997,13 @@ def find_post(request, user_pk): return render_template("forum/find/post.html", { "posts": shown_posts, - "usr": u, + "usr": displayed_user, "pages": paginator_range(page, paginator.num_pages), "nb": page, }) @login_required - def followed_topics(request): followed_topics = request.user.get_profile().get_followed_topics() diff --git a/zds/gallery/forms.py b/zds/gallery/forms.py index 644fd4e71b..7969ea1bb5 100644 --- a/zds/gallery/forms.py +++ b/zds/gallery/forms.py @@ -104,7 +104,6 @@ def clean(self): return cleaned_data - class ImageForm(forms.Form): title = forms.CharField( label='Titre', @@ -121,9 +120,7 @@ class ImageForm(forms.Form): physical = forms.ImageField( label=u'Sélectionnez votre image', required=True, - help_text='Taille maximum : ' - + str(settings.IMAGE_MAX_SIZE) - + ' megabytes' + help_text='Taille maximum : ' + str(settings.IMAGE_MAX_SIZE / 1024) + ' <abbr title="kibioctet">Kio</abbr>' ) def __init__(self, *args, **kwargs): @@ -143,6 +140,37 @@ def __init__(self, *args, **kwargs): ), ) + def clean(self): + cleaned_data = super(ImageForm, self).clean() + + physical = cleaned_data.get('physical') + + if physical is not None and physical.size > settings.IMAGE_MAX_SIZE: + self._errors['physical'] = self.error_class([u'Votre image est trop lourde, la limite autorisée est de : {0} Ko' + .format(settings.IMAGE_MAX_SIZE / 1024) + ' Ko']) + return cleaned_data + + +class UpdateImageForm(ImageForm): + def __init__(self, *args, **kwargs): + super(ImageForm, self).__init__(*args, **kwargs) + + self.fields['physical'].required = False + + self.helper = FormHelper() + self.helper.form_class = 'clearfix' + self.helper.form_method = 'post' + + self.helper.layout = Layout( + Field('title'), + Field('legend'), + Field('physical'), + ButtonHolder( + StrictButton(u'Mettre à jour', type='submit'), + HTML('<a class="btn btn-cancel" ' + u'href="{{ gallery.get_absolute_url }}">Annuler</a>'), + ), + ) class ImageAsAvatarForm(forms.Form): """"Form to add current image as avatar""" diff --git a/zds/gallery/tests/tests_forms.py b/zds/gallery/tests/tests_forms.py index 538fb34d1b..3f8d5109e1 100644 --- a/zds/gallery/tests/tests_forms.py +++ b/zds/gallery/tests/tests_forms.py @@ -82,6 +82,96 @@ def test_valid_image_form(self): self.assertTrue(form.is_valid()) upload_file.close() + def test_empty_title_image_form(self): + upload_file = open(os.path.join(settings.SITE_ROOT, 'fixtures', 'logo.png'), 'r') + + data = { + 'title': '', + 'legend': 'Test Legend', + } + + files = { + 'physical': SimpleUploadedFile(upload_file.name, upload_file.read()) + } + form = ImageForm(data, files) + + self.assertFalse(form.is_valid()) + upload_file.close() + + def test_empty_pic_image_form(self): + + data = { + 'title': 'Test Title', + 'legend': 'Test Legend', + } + + files = { + 'physical': '' + } + form = ImageForm(data, files) + + self.assertFalse(form.is_valid()) + + def test_too_big_pic_image_form(self): + upload_file = open(os.path.join(settings.SITE_ROOT, 'fixtures', 'image_test.jpg'), 'r') + + data = { + 'title': 'Test Title', + 'legend': 'Test Legend', + } + + files = { + 'physical': SimpleUploadedFile(upload_file.name, upload_file.read()) + } + form = ImageForm(data, files) + + self.assertFalse(form.is_valid()) + upload_file.close() + + def test_bot_a_pic_image_form(self): + upload_file = open(os.path.join(settings.SITE_ROOT, 'fixtures', 'forums.yaml'), 'r') + + data = { + 'title': 'Test Title', + 'legend': 'Test Legend', + } + + files = { + 'physical': SimpleUploadedFile(upload_file.name, upload_file.read()) + } + form = ImageForm(data, files) + + self.assertFalse(form.is_valid()) + upload_file.close() + + def test_too_long_title_image_form(self): + upload_file = open(os.path.join(settings.SITE_ROOT, 'fixtures', 'logo.png'), 'r') + + data = { + 'title': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam condimentum enim amet.', + 'legend': 'Test Legend', + } + + files = { + 'physical': SimpleUploadedFile(upload_file.name, upload_file.read()) + } + form = ImageForm(data, files) + upload_file.close() + + def test_too_long_legend_image_form(self): + upload_file = open(os.path.join(settings.SITE_ROOT, 'fixtures', 'logo.png'), 'r') + + data = { + 'title': 'Test Title', + 'legend': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam condimentum enim amet.', + } + + files = { + 'physical': SimpleUploadedFile(upload_file.name, upload_file.read()) + } + form = ImageForm(data, files) + upload_file.close() + class ImageAsAvatarFormTest(TestCase): diff --git a/zds/gallery/tests/tests_views.py b/zds/gallery/tests/tests_views.py index 81ad2a706a..9f43e3559c 100644 --- a/zds/gallery/tests/tests_views.py +++ b/zds/gallery/tests/tests_views.py @@ -365,17 +365,17 @@ def test_success_member_edit_image(self): with open(os.path.join(settings.SITE_ROOT, 'fixtures', 'logo.png'), 'r') as fp: response = self.client.post( - reverse( - 'zds.gallery.views.edit_image', - args=[self.gallery.pk, self.image.pk] - ), - { - 'title': 'edit title', - 'legend': 'dit legend', - 'slug': 'edit-slug', - 'physical': fp - }, - follow=True + reverse( + 'zds.gallery.views.edit_image', + args=[self.gallery.pk, self.image.pk] + ), + { + 'title': 'edit title', + 'legend': 'dit legend', + 'slug': 'edit-slug', + 'physical': fp + }, + follow=True ) self.assertEqual(200, response.status_code) image_test = Image.objects.get(pk=self.image.pk) @@ -415,17 +415,17 @@ def tearDown(self): self.image3.delete() def test_denies_anonymous(self): - response = self.client.get(reverse('zds.gallery.views.modify_image'), follow=True) + response = self.client.get(reverse('zds.gallery.views.delete_image'), follow=True) self.assertRedirects(response, reverse('zds.member.views.login_view') - + '?next=' + urllib.quote(reverse('zds.gallery.views.modify_image'), '')) + + '?next=' + urllib.quote(reverse('zds.gallery.views.delete_image'), '')) def test_fail_modify_image_with_no_permission(self): login_check = self.client.login(username=self.profile3.user.username, password='hostel77') self.assertTrue(login_check) response = self.client.post( - reverse('zds.gallery.views.modify_image'), + reverse('zds.gallery.views.delete_image'), { 'gallery': self.gallery1.pk, }, @@ -445,7 +445,7 @@ def test_delete_image_from_other_user(self): self.assertTrue(login_check) self.client.post( - reverse('zds.gallery.views.modify_image'), + reverse('zds.gallery.views.delete_image'), { 'gallery': self.gallery1.pk, 'delete': '', @@ -462,7 +462,7 @@ def test_success_delete_image_write_permission(self): self.assertTrue(login_check) response = self.client.post( - reverse('zds.gallery.views.modify_image'), + reverse('zds.gallery.views.delete_image'), { 'gallery': self.gallery1.pk, 'delete': '', @@ -479,7 +479,7 @@ def test_success_delete_list_images_write_permission(self): self.assertTrue(login_check) response = self.client.post( - reverse('zds.gallery.views.modify_image'), + reverse('zds.gallery.views.delete_image'), { 'gallery': self.gallery1.pk, 'delete_multi': '', @@ -497,7 +497,7 @@ def test_fail_delete_image_read_permission(self): self.assertTrue(login_check) response = self.client.post( - reverse('zds.gallery.views.modify_image'), + reverse('zds.gallery.views.delete_image'), { 'gallery': self.gallery1.pk, 'delete': '', diff --git a/zds/gallery/urls.py b/zds/gallery/urls.py index 7210713a1e..c343547b57 100644 --- a/zds/gallery/urls.py +++ b/zds/gallery/urls.py @@ -18,7 +18,7 @@ url(r'^image/ajouter/(?P<gal_pk>\d+)/$', 'zds.gallery.views.new_image'), url(r'^image/modifier/$', - 'zds.gallery.views.modify_image'), + 'zds.gallery.views.delete_image'), url(r'^image/editer/(?P<gal_pk>\d+)/(?P<img_pk>\d+)/$', 'zds.gallery.views.edit_image'), ) diff --git a/zds/gallery/views.py b/zds/gallery/views.py index 03c716948c..841ac9a037 100644 --- a/zds/gallery/views.py +++ b/zds/gallery/views.py @@ -11,7 +11,7 @@ from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.shortcuts import redirect, get_object_or_404 -from zds.gallery.forms import ImageForm, GalleryForm, UserGalleryForm, ImageAsAvatarForm +from zds.gallery.forms import ImageForm, UpdateImageForm, GalleryForm, UserGalleryForm, ImageAsAvatarForm from zds.gallery.models import UserGallery, Image, Gallery from zds.tutorial.models import Tutorial from zds.member.decorator import can_write_and_read_now @@ -169,13 +169,14 @@ def modify_gallery(request): @login_required +@can_write_and_read_now def edit_image(request, gal_pk, img_pk): - """Creates a new image.""" + """Edit an existing image.""" gal = get_object_or_404(Gallery, pk=gal_pk) img = get_object_or_404(Image, pk=img_pk) - # check if user can edit image + # Check if user can edit image try: permission = UserGallery.objects.get(user=request.user, gallery=gal) if permission.mode != 'W': @@ -183,17 +184,17 @@ def edit_image(request, gal_pk, img_pk): except: raise PermissionDenied - # check if the image belong to the gallery + # Check if the image belong to the gallery if img.gallery != gal: raise PermissionDenied - - + if request.method == "POST": - form = ImageForm(request.POST, request.FILES) + form = UpdateImageForm(request.POST, request.FILES) if form.is_valid(): if "physical" in request.FILES: if request.FILES["physical"].size > settings.IMAGE_MAX_SIZE: - messages.error(request, "Votre image est beaucoup trop lourde, réduidez sa taille à moins de {} avant de l'envoyer".format(str(settings.IMAGE_MAX_SIZE/1024*1024))) + messages.error(request, "Votre image est beaucoup trop lourde, réduisez sa taille à moins de {} \ + <abbr title=\"kibioctet\">Kio</abbr> avant de l'envoyer".format(str(settings.IMAGE_MAX_SIZE/1024))) else: img.title = request.POST["title"] img.legend = request.POST["legend"] @@ -201,19 +202,24 @@ def edit_image(request, gal_pk, img_pk): img.slug = slugify(request.FILES["physical"]) img.update = datetime.now() img.save() - # Redirect to the document list after POST - return redirect(gal.get_absolute_url()) + # Redirect to the newly uploaded image edit page after POST + return redirect(reverse("zds.gallery.views.edit_image", args=[gal.pk, img.pk])) else: img.title = request.POST["title"] img.legend = request.POST["legend"] img.update = datetime.now() img.save() + # Redirect to the newly uploaded image edit page after POST + return redirect(reverse("zds.gallery.views.edit_image", args=[gal.pk, img.pk])) - # Redirect to the document list after POST - return redirect(gal.get_absolute_url()) else: - form = ImageForm(initial={"title": img.title, "legend": img.legend, "physical": img.physical}) - + form = UpdateImageForm(initial={ + "title": img.title, + "legend": img.legend, + "physical": img.physical, + "new_image": False, + }) + as_avatar_form = ImageAsAvatarForm() return render_template( "gallery/image/edit.html", { @@ -226,12 +232,9 @@ def edit_image(request, gal_pk, img_pk): @can_write_and_read_now @login_required -def modify_image(request): - - # We only handle secured POST actions +@require_POST +def delete_image(request): - if request.method != "POST": - raise Http404 try: gal_pk = request.POST["gallery"] except KeyError: @@ -241,7 +244,6 @@ def modify_image(request): gal_mode = UserGallery.objects.get(gallery=gal, user=request.user) # Only allow RW users to modify images - if gal_mode.mode != "W": raise PermissionDenied except: @@ -275,8 +277,7 @@ def new_image(request, gal_pk): if request.method == "POST": form = ImageForm(request.POST, request.FILES) - if form.is_valid() and request.FILES["physical"].size \ - < settings.IMAGE_MAX_SIZE: + if form.is_valid(): img = Image() img.physical = request.FILES["physical"] img.gallery = gal @@ -287,13 +288,12 @@ def new_image(request, gal_pk): img.save() # Redirect to the newly uploaded image edit page after POST - return redirect(reverse("zds.gallery.views.edit_image", args=[gal.pk, img.pk])) else: return render_template("gallery/image/new.html", {"form": form, "gallery": gal}) else: - form = ImageForm() # A empty, unbound form + form = ImageForm(initial={"new_image": True}) # A empty, unbound form return render_template("gallery/image/new.html", {"form": form, "gallery": gal}) diff --git a/zds/member/models.py b/zds/member/models.py index 24af43256f..101db079a2 100644 --- a/zds/member/models.py +++ b/zds/member/models.py @@ -118,7 +118,7 @@ def get_avatar_url(self): return self.avatar_url else: return 'https://secure.gravatar.com/avatar/{0}?d=identicon'.format( - md5(self.user.email).hexdigest()) + md5(self.user.email.lower()).hexdigest()) def get_post_count(self): """Number of messages posted.""" diff --git a/zds/member/views.py b/zds/member/views.py index 2e25d36f63..e6f664dbfb 100644 --- a/zds/member/views.py +++ b/zds/member/views.py @@ -15,8 +15,9 @@ from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage from django.core.urlresolvers import reverse from django.db import transaction +from django.db.models import Q from django.http import Http404, HttpResponse -from django.shortcuts import redirect, get_object_or_404, render_to_response +from django.shortcuts import redirect, get_object_or_404 from django.template import Context, RequestContext from django.template.loader import get_template from django.views.decorators.http import require_POST @@ -122,16 +123,13 @@ def details(request, user_name): .filter(authors__in=[usr]) \ .order_by("-pubdate" ).all() - my_topics = Topic.objects.filter(author__pk=usr.pk).order_by("-pubdate" - ).all() - tops = [] - for top in my_topics: - if not top.forum.can_read(request.user): - continue - else: - tops.append(top) - if len(tops) >= 5: - break + my_topics = \ + Topic.objects\ + .filter(author=usr)\ + .exclude(Q(forum__group__isnull=False) & ~Q(forum__group__in=request.user.groups.all()))\ + .prefetch_related("author")\ + .order_by("-pubdate").all() + form = OldTutoForm(profile) oldtutos = [] if profile.sdz_tutorial: @@ -146,7 +144,7 @@ def details(request, user_name): "bans": bans, "articles": my_articles[:5], "tutorials": my_tutorials[:5], - "topics": tops, + "topics": my_topics[:5], "form": form, "old_tutos": oldtutos, }) @@ -361,8 +359,7 @@ def settings_mini_profile(request, user_name): return redirect(reverse("zds.member.views.details", args=[profile.user.username])) else: - return render_to_response("member/settings/profile.html", c, - RequestContext(request)) + return render_template("member/settings/profile.html", c) else: form = MiniProfileForm(initial={ "biography": profile.biography, @@ -371,8 +368,7 @@ def settings_mini_profile(request, user_name): "sign": profile.sign, }) c = {"form": form, "profile": profile} - return render_to_response("member/settings/profile.html", c, - RequestContext(request)) + return render_template("member/settings/profile.html", c) @can_write_and_read_now @@ -411,8 +407,7 @@ def settings_profile(request): "Le profil a correctement été mis à jour.") return redirect(reverse("zds.member.views.settings_profile")) else: - return render_to_response("member/settings/profile.html", c, - RequestContext(request)) + return render_template("member/settings/profile.html", c) else: form = ProfileForm(initial={ "biography": profile.biography, @@ -425,8 +420,7 @@ def settings_profile(request): "sign": profile.sign, }) c = {"form": form} - return render_to_response("member/settings/profile.html", c, - RequestContext(request)) + return render_template("member/settings/profile.html", c) @can_write_and_read_now @@ -471,13 +465,11 @@ def settings_account(request): messages.error(request, "Une erreur est survenue.") return redirect(reverse("zds.member.views.settings_account")) else: - return render_to_response("member/settings/account.html", c, - RequestContext(request)) + return render_template("member/settings/account.html", c) else: form = ChangePasswordForm(request.user) c = {"form": form} - return render_to_response("member/settings/account.html", c, - RequestContext(request)) + return render_template("member/settings/account.html", c) @can_write_and_read_now @@ -499,13 +491,11 @@ def settings_user(request): old.save() return redirect(old.profile.get_absolute_url()) else: - return render_to_response("member/settings/user.html", c, - RequestContext(request)) + return render_template("member/settings/user.html", c) else: form = ChangeUserForm() c = {"form": form} - return render_to_response("member/settings/user.html", c, - RequestContext(request)) + return render_template("member/settings/user.html", c) diff --git a/zds/mp/views.py b/zds/mp/views.py index 4c53d8dae0..88d83022f8 100644 --- a/zds/mp/views.py +++ b/zds/mp/views.py @@ -348,9 +348,10 @@ def answer(request): for line in post_cite.text.splitlines(): text = text + '> ' + line + '\n' - text = u'{0}Source:[{1}]({2})'.format( + text = u'{0}Source:[{1}]({2}{3})'.format( text, post_cite.author.username, + settings.SITE_URL, post_cite.get_absolute_url()) form = PrivatePostForm(g_topic, request.user, initial={ diff --git a/zds/pages/forms.py b/zds/pages/forms.py index cd87ddfe91..b7df380f1d 100644 --- a/zds/pages/forms.py +++ b/zds/pages/forms.py @@ -9,13 +9,13 @@ class AssocSubscribeForm(forms.Form): first_name = forms.CharField( label=u'Prénom', - max_length= 30, + max_length=30, required=True, ) surname = forms.CharField( label='Nom de famille', - max_length= 30, + max_length=30, required=True, ) @@ -26,31 +26,31 @@ class AssocSubscribeForm(forms.Form): adresse = forms.CharField( label=u'Adresse', - max_length= 38, + max_length=38, required=True, ) adresse_complement = forms.CharField( label=u'Complément d\'adresse', - max_length= 38, + max_length=38, required=False, ) code_postal = forms.CharField( label='Code Postal', - max_length= 5, + max_length=10, required=True, ) ville = forms.CharField( label='Ville', - max_length= 38, + max_length=38, required=True, ) pays = forms.CharField( label='Pays', - max_length= 38, + max_length=38, required=True, ) diff --git a/zds/pages/tests.py b/zds/pages/tests.py index a9f8267b4c..7a584cb141 100644 --- a/zds/pages/tests.py +++ b/zds/pages/tests.py @@ -246,3 +246,12 @@ def test_url_cookies(self): ) self.assertEqual(result.status_code, 200) + + def test_render_template(self): + """Test: render_template() works and git_version is in template.""" + + result = self.client.get( + reverse('zds.pages.views.home'), + ) + + self.assertTrue('git_version' in result.context[-1]) diff --git a/zds/search/views.py b/zds/search/views.py index 4cfe0b2b2a..489bc8fd8f 100644 --- a/zds/search/views.py +++ b/zds/search/views.py @@ -1,9 +1,9 @@ # coding: utf-8 -from django.shortcuts import render_to_response from haystack.views import SearchView -from zds.utils.paginator import paginator_range from zds.search.constants import MODEL_NAMES +from zds.utils.paginator import paginator_range +from zds.utils import render_template class CustomSearchView(SearchView): def create_response(self): @@ -26,4 +26,4 @@ def create_response(self): context['suggestion'] = self.form.get_suggestion() context.update(self.extra_context()) - return render_to_response(self.template, context, context_instance=self.context_class(self.request)) \ No newline at end of file + return render_template(self.template, context) diff --git a/zds/tutorial/forms.py b/zds/tutorial/forms.py index 86f30a3788..b5a7ea2ff2 100644 --- a/zds/tutorial/forms.py +++ b/zds/tutorial/forms.py @@ -387,9 +387,8 @@ def __init__(self, *args, **kwargs): StrictButton( 'Confirmer', type='submit'), - Hidden( - 'tutorial', '{{ tutorial.pk }}'), Hidden( - 'version', '{{ version }}'), ) + Hidden('tutorial', '{{ tutorial.pk }}'), + Hidden('version', '{{ version }}'), ) class ValidForm(forms.Form): diff --git a/zds/tutorial/models.py b/zds/tutorial/models.py index b718f9e920..a429fd318d 100644 --- a/zds/tutorial/models.py +++ b/zds/tutorial/models.py @@ -162,7 +162,7 @@ def in_drafting(self): return (self.sha_draft is not None) and (self.sha_draft.strip() != '') def on_line(self): - return (self.sha_public is not None) and (self.sha_public.strip() != '') + return (self.sha_public is not None) and (self.sha_public.strip() != '') def is_mini(self): return self.type == 'MINI' @@ -196,6 +196,10 @@ def load_dic(self, mandata): mandata['image'] = self.image mandata['pubdate'] = self.pubdate mandata['source'] = self.source + mandata['have_markdown'] = self.have_markdown() + mandata['have_html'] = self.have_html() + mandata['have_pdf'] = self.have_pdf() + mandata['have_epub'] = self.have_epub() return mandata @@ -358,6 +362,22 @@ def update_children(self): if chapter: chapter.update_children() + def have_markdown(self): + return os.path.isfile(os.path.join(self.get_prod_path(), + self.slug + + ".md")) + def have_html(self): + return os.path.isfile(os.path.join(self.get_prod_path(), + self.slug + + ".html")) + def have_pdf(self): + return os.path.isfile(os.path.join(self.get_prod_path(), + self.slug + + ".pdf")) + def have_epub(self): + return os.path.isfile(os.path.join(self.get_prod_path(), + self.slug + + ".epub")) def get_last_tutorials(): tutorials = Tutorial.objects.all()\ @@ -775,8 +795,12 @@ def get_conclusion_online(self): return None def update_children(self): - self.introduction = os.path.join(self.get_phy_slug(), "introduction.md") - self.conclusion = os.path.join(self.get_phy_slug(), "conclusion.md") + if self.part: + self.introduction = os.path.join(self.part.get_phy_slug(), self.get_phy_slug(), "introduction.md") + self.conclusion = os.path.join(self.part.get_phy_slug(), self.get_phy_slug(), "conclusion.md") + else: + self.introduction = os.path.join("introduction.md") + self.conclusion = os.path.join("conclusion.md") self.save() for extract in self.get_extracts(): extract.save() diff --git a/zds/tutorial/tests.py b/zds/tutorial/tests.py index 3fffb17498..1e3493dd41 100644 --- a/zds/tutorial/tests.py +++ b/zds/tutorial/tests.py @@ -113,6 +113,7 @@ def setUp(self): 'source': 'http://zestedesavoir.com', }, follow=False) + self.bigtuto = Tutorial.objects.get(pk=self.bigtuto.pk) self.assertEqual(pub.status_code, 302) self.assertEquals(len(mail.outbox), 1) @@ -501,31 +502,59 @@ def test_workflow_tuto(self): self.assertEqual(result.status_code, 302) self.assertEqual(Tutorial.objects.all().count(), 2) tuto = Tutorial.objects.last() - #add part + #add part 1 result = self.client.post( reverse('zds.tutorial.views.add_part') + '?tutoriel={}'.format(tuto.pk), { 'title': u"Partie 1", - 'introduction':"Présentation", - 'conclusion': "", + 'introduction':u"Présentation", + 'conclusion': u"Fin de la présenation", }, follow=False) self.assertEqual(result.status_code, 302) self.assertEqual(Part.objects.filter(tutorial=tuto).count(), 1) p1 = Part.objects.filter(tutorial=tuto).last() - #add part + + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_part', + args=[ + tuto.pk, + tuto.slug, + p1.pk, + p1.slug]), + follow=True) + self.assertContains(response=result, text = u"Présentation") + self.assertContains(response=result, text = u"Fin de la présenation") + + #add part 2 result = self.client.post( reverse('zds.tutorial.views.add_part') + '?tutoriel={}'.format(tuto.pk), { 'title': u"Partie 2", - 'introduction':"Analyse", - 'conclusion': "", + 'introduction': u"Analyse", + 'conclusion': u"Fin de l'analyse", }, follow=False) self.assertEqual(result.status_code, 302) self.assertEqual(Part.objects.filter(tutorial=tuto).count(), 2) p2 = Part.objects.filter(tutorial=tuto).last() - #add part + + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_part', + args=[ + tuto.pk, + tuto.slug, + p2.pk, + p2.slug]), + follow=True) + self.assertContains(response=result, text = u"Analyse") + self.assertContains(response=result, text = u"Fin de l'analyse") + + #add part 3 result = self.client.post( reverse('zds.tutorial.views.add_part') + '?tutoriel={}'.format(tuto.pk), { @@ -537,49 +566,111 @@ def test_workflow_tuto(self): self.assertEqual(result.status_code, 302) self.assertEqual(Part.objects.filter(tutorial=tuto).count(), 3) p3 = Part.objects.filter(tutorial=tuto).last() - #add chapter + + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_part', + args=[ + tuto.pk, + tuto.slug, + p3.pk, + p3.slug]), + follow=True) + self.assertContains(response=result, text = u"Expérimentation") + self.assertContains(response=result, text = u"C'est terminé") + + #add chapter 1 for part 2 result = self.client.post( reverse('zds.tutorial.views.add_chapter') + '?partie={}'.format(p2.pk), { 'title': u"Chapitre 1", 'introduction':"Mon premier chapitre", - 'conclusion': "", + 'conclusion': "Fin de mon premier chapitre", }, follow=False) self.assertEqual(result.status_code, 302) self.assertEqual(Chapter.objects.filter(part=p1).count(), 0) self.assertEqual(Chapter.objects.filter(part=p2).count(), 1) self.assertEqual(Chapter.objects.filter(part=p3).count(), 0) + c1 = Chapter.objects.filter(part=p2).last() - #add chapter + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_chapter', + args=[ + tuto.pk, + tuto.slug, + p2.pk, + p2.slug, + c1.pk, + c1.slug]), + follow=True) + self.assertContains(response=result, text = u"Mon premier chapitre") + self.assertContains(response=result, text = u"Fin de mon premier chapitre") + + #add chapter 2 for part 2 result = self.client.post( reverse('zds.tutorial.views.add_chapter') + '?partie={}'.format(p2.pk), { 'title': u"Chapitre 2", - 'introduction':"Mon deuxième chapitre", - 'conclusion': "", + 'introduction': u"Mon deuxième chapitre", + 'conclusion':u"Fin de mon deuxième chapitre", }, follow=False) self.assertEqual(result.status_code, 302) self.assertEqual(Chapter.objects.filter(part=p1).count(), 0) self.assertEqual(Chapter.objects.filter(part=p2).count(), 2) self.assertEqual(Chapter.objects.filter(part=p3).count(), 0) + c2 = Chapter.objects.filter(part=p2).last() - #add chapter + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_chapter', + args=[ + tuto.pk, + tuto.slug, + p2.pk, + p2.slug, + c2.pk, + c2.slug]), + follow=True) + self.assertContains(response=result, text = u"Mon deuxième chapitre") + self.assertContains(response=result, text = u"Fin de mon deuxième chapitre") + + #add chapter 3 for part 2 result = self.client.post( reverse('zds.tutorial.views.add_chapter') + '?partie={}'.format(p2.pk), { 'title': u"Chapitre 2", - 'introduction':"Mon troisième chapitre homonyme", - 'conclusion': "", + 'introduction': u"Mon troisième chapitre homonyme", + 'conclusion': u"Fin de mon troisième chapitre", }, follow=False) self.assertEqual(result.status_code, 302) self.assertEqual(Chapter.objects.filter(part=p1).count(), 0) self.assertEqual(Chapter.objects.filter(part=p2).count(), 3) self.assertEqual(Chapter.objects.filter(part=p3).count(), 0) + c3 = Chapter.objects.filter(part=p2).last() - #add chapter + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_chapter', + args=[ + tuto.pk, + tuto.slug, + p2.pk, + p2.slug, + c3.pk, + c3.slug]), + follow=True) + self.assertContains(response=result, text = u"Mon troisième chapitre homonyme") + self.assertContains(response=result, text = u"Fin de mon troisième chapitre") + + #add chapter 4 for part 1 result = self.client.post( reverse('zds.tutorial.views.add_chapter') + '?partie={}'.format(p1.pk), { @@ -592,6 +683,258 @@ def test_workflow_tuto(self): self.assertEqual(Chapter.objects.filter(part=p1).count(), 1) self.assertEqual(Chapter.objects.filter(part=p2).count(), 3) self.assertEqual(Chapter.objects.filter(part=p3).count(), 0) + c4 = Chapter.objects.filter(part=p1).last() + + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_chapter', + args=[ + tuto.pk, + tuto.slug, + p1.pk, + p1.slug, + c4.pk, + c4.slug]), + follow=True) + self.assertContains(response=result, text = u"Mon premier chapitre d'une autre partie") + + #edit part 2 + result = self.client.post( + reverse('zds.tutorial.views.edit_part') + '?partie={}'.format(p2.pk), + { + 'title': u"Partie 2 : edition de titre", + 'introduction': u"Expérimentation : edition d'introduction", + 'conclusion': u"C'est terminé : edition de conlusion", + }, + follow=True) + self.assertContains(response=result, text = u"Partie 2 : edition de titre") + self.assertContains(response=result, text = u"Expérimentation : edition d'introduction") + self.assertContains(response=result, text = u"C'est terminé : edition de conlusion") + self.assertEqual(Part.objects.filter(tutorial=tuto).count(), 3) + + #edit chapter 3 + result = self.client.post( + reverse('zds.tutorial.views.edit_chapter') + '?chapitre={}'.format(c3.pk), + { + 'title': u"Chapitre 3 : edition de titre", + 'introduction': u"Edition d'introduction", + 'conclusion': u"Edition de conlusion", + }, + follow=True) + self.assertContains(response=result, text = u"Chapitre 3 : edition de titre") + self.assertContains(response=result, text = u"Edition d'introduction") + self.assertContains(response=result, text = u"Edition de conlusion") + self.assertEqual(Chapter.objects.filter(part=p2.pk).count(), 3) + + #edit part 2 + result = self.client.post( + reverse('zds.tutorial.views.edit_part') + '?partie={}'.format(p2.pk), + { + 'title': u"Partie 2 : seconde edition de titre", + 'introduction': u"Expérimentation : seconde edition d'introduction", + 'conclusion': u"C'est terminé : seconde edition de conlusion", + }, + follow=True) + self.assertContains(response=result, text = u"Partie 2 : seconde edition de titre") + self.assertContains(response=result, text = u"Expérimentation : seconde edition d'introduction") + self.assertContains(response=result, text = u"C'est terminé : seconde edition de conlusion") + self.assertEqual(Part.objects.filter(tutorial=tuto).count(), 3) + + #edit chapter 2 + result = self.client.post( + reverse('zds.tutorial.views.edit_chapter') + '?chapitre={}'.format(c2.pk), + { + 'title': u"Chapitre 2 : edition de titre", + 'introduction': u"Edition d'introduction", + 'conclusion': u"Edition de conlusion", + }, + follow=True) + self.assertContains(response=result, text = u"Chapitre 2 : edition de titre") + self.assertContains(response=result, text = u"Edition d'introduction") + self.assertContains(response=result, text = u"Edition de conlusion") + self.assertEqual(Chapter.objects.filter(part=p2.pk).count(), 3) + + #move chapter 1 against 2 + result = self.client.post( + reverse('zds.tutorial.views.modify_chapter'), + { + 'chapter': c1.pk, + 'move_target': c2.position_in_part, + }, + follow=True) + #move part 1 against 2 + result = self.client.post( + reverse('zds.tutorial.views.modify_part'), + { + 'part': p1.pk, + 'move_target': p2.position_in_tutorial, + }, + follow=True) + self.assertEqual(Chapter.objects.filter(part__tutorial=tuto.pk).count(), 4) + + # ask public tutorial + tuto = Tutorial.objects.get(pk=tuto.pk) + pub = self.client.post( + reverse('zds.tutorial.views.ask_validation'), + { + 'tutorial': tuto.pk, + 'text': u'Ce tuto est excellent', + 'version': tuto.sha_draft, + 'source': '', + }, + follow=False) + self.assertEqual(pub.status_code, 302) + + # logout before + self.client.logout() + # login with staff member + self.assertEqual( + self.client.login( + username=self.staff.username, + password='hostel77'), + True) + + # reserve tutorial + validation = Validation.objects.filter( + tutorial__pk=tuto.pk).last() + pub = self.client.post( + reverse('zds.tutorial.views.reservation', args=[validation.pk]), + follow=False) + self.assertEqual(pub.status_code, 302) + + # publish tutorial + pub = self.client.post( + reverse('zds.tutorial.views.valid_tutorial'), + { + 'tutorial': tuto.pk, + 'text': u'Ce tuto est excellent', + 'is_major': True, + 'source': 'http://zestedesavoir.com', + }, + follow=False) + + #delete part 1 + result = self.client.post( + reverse('zds.tutorial.views.modify_part'), + { + 'part': p1.pk, + 'delete': "OK", + }, + follow=True) + #delete chapter 3 + result = self.client.post( + reverse('zds.tutorial.views.modify_chapter'), + { + 'chapter': c3.pk, + 'delete': "OK", + }, + follow=True) + self.assertEqual(Chapter.objects.filter(part__tutorial=tuto.pk).count(), 2) + self.assertEqual(Part.objects.filter(tutorial=tuto.pk).count(), 2) + + # ask public tutorial + tuto = Tutorial.objects.get(pk=tuto.pk) + pub = self.client.post( + reverse('zds.tutorial.views.ask_validation'), + { + 'tutorial': tuto.pk, + 'text': u'Ce tuto est excellent', + 'version': tuto.sha_draft, + 'source': '', + }, + follow=False) + self.assertEqual(pub.status_code, 302) + + # reserve tutorial + validation = Validation.objects.filter( + tutorial__pk=tuto.pk).last() + pub = self.client.post( + reverse('zds.tutorial.views.reservation', args=[validation.pk]), + follow=False) + self.assertEqual(pub.status_code, 302) + + # publish tutorial + pub = self.client.post( + reverse('zds.tutorial.views.valid_tutorial'), + { + 'tutorial': tuto.pk, + 'text': u'Ce tuto est excellent', + 'is_major': True, + 'source': 'http://zestedesavoir.com', + }, + follow=False) + #check view online delete part and chapter + result = self.client.get( + reverse( + 'zds.tutorial.views.view_part', + args=[ + tuto.pk, + tuto.slug, + p1.pk, + p1.slug]), + follow=True) + self.assertEqual(result.status_code, 404) + + result = self.client.get( + reverse( + 'zds.tutorial.views.view_chapter', + args=[ + tuto.pk, + tuto.slug, + p2.pk, + p2.slug, + c3.pk, + c3.slug]), + follow=True) + self.assertEqual(result.status_code, 404) + + def test_available_tuto(self): + """ Test that all page of big tutorial is available""" + parts = self.bigtuto.get_parts() + for part in parts: + result = self.client.get(reverse( + 'zds.tutorial.views.view_part_online', + args=[ + self.bigtuto.pk, + self.bigtuto.slug, + part.pk, + part.slug]), + follow=True) + self.assertEqual(result.status_code, 200) + result = self.client.get(reverse( + 'zds.tutorial.views.view_part', + args=[ + self.bigtuto.pk, + self.bigtuto.slug, + part.pk, + part.slug]), + follow=True) + self.assertEqual(result.status_code, 200) + chapters = part.get_chapters() + for chapter in chapters: + result = self.client.get(reverse( + 'zds.tutorial.views.view_chapter_online', + args=[ + self.bigtuto.pk, + self.bigtuto.slug, + part.pk, + part.slug, + chapter.pk, + chapter.slug]), + follow=True) + self.assertEqual(result.status_code, 200) + result = self.client.get(reverse( + 'zds.tutorial.views.view_chapter', + args=[ + self.bigtuto.pk, + self.bigtuto.slug, + part.pk, + part.slug, + chapter.pk, + chapter.slug]), + follow=True) + self.assertEqual(result.status_code, 200) def test_url_for_member(self): """Test simple get request by simple member.""" @@ -936,7 +1279,7 @@ def test_delete_image_tutorial(self): # Delete the image of the bigtuto. response = self.client.post( - reverse('zds.gallery.views.modify_image'), + reverse('zds.gallery.views.delete_image'), { 'gallery': self.bigtuto.gallery.pk, 'delete_multi': '', @@ -1117,6 +1460,30 @@ def test_workflow_beta_tuto(self) : self.client.get(url).status_code, 200) + def test_gallery_tuto_change_name(self): + """ + Test if the gallery attached to a tutorial change its name when the tuto + has the name changed. + """ + + newtitle = u"The New Title" + response = self.client.post( + reverse('zds.tutorial.views.edit_tutorial') + '?tutoriel={}'.format(self.bigtuto.pk), + { + 'title': newtitle, + 'subcategory': self.subcat.pk, + 'introduction': self.bigtuto.introduction, + 'description': self.bigtuto.description, + 'conclusion': self.bigtuto.conclusion, + }, + follow=True) + + tuto = Tutorial.objects.filter(pk=self.bigtuto.pk).first() + self.assertEqual(tuto.title, newtitle) + self.assertEqual(tuto.gallery.title, tuto.title) + self.assertEqual(tuto.gallery.slug, tuto.slug) + + def tearDown(self): if os.path.isdir(settings.REPO_PATH): shutil.rmtree(settings.REPO_PATH) @@ -1151,6 +1518,8 @@ def setUp(self): self.user = ProfileFactory().user self.staff = StaffProfileFactory().user + self.subcat = SubCategoryFactory() + self.minituto = MiniTutorialFactory() self.minituto.authors.add(self.user_author) self.minituto.gallery = GalleryFactory() @@ -1198,6 +1567,8 @@ def setUp(self): }, follow=False) self.assertEqual(pub.status_code, 302) + self.minituto = Tutorial.objects.get(pk=self.minituto.pk) + self.assertEqual(self.minituto.on_line(), True) self.assertEquals(len(mail.outbox), 1) mail.outbox = [] @@ -1759,7 +2130,7 @@ def test_delete_image_tutorial(self): # Delete the image of the minituto. response = self.client.post( - reverse('zds.gallery.views.modify_image'), + reverse('zds.gallery.views.delete_image'), { 'gallery': self.minituto.gallery.pk, 'delete_multi': '', @@ -2032,6 +2403,138 @@ def test_workflow_beta_tuto(self) : self.client.get(url).status_code, 200) + def test_workflow_tuto(self): + """Test workflow of mini tutorial.""" + + # logout before + self.client.logout() + # login with simple member + self.assertEqual( + self.client.login( + username=self.user.username, + password='hostel77'), + True) + + #add new mini tuto + result = self.client.post( + reverse('zds.tutorial.views.add_tutorial'), + { + 'title': u"Introduction à l'algèbre", + 'description': "Perçer les mystère de boole", + 'introduction':"Bienvenue dans le monde binaires", + 'conclusion': "", + 'type': "MINI", + 'subcategory': self.subcat.pk, + }, + follow=False) + + self.assertEqual(result.status_code, 302) + self.assertEqual(Tutorial.objects.all().count(), 2) + tuto = Tutorial.objects.last() + chapter = Chapter.objects.filter(tutorial__pk=tuto.pk).first() + + #add extract 1 + result = self.client.post( + reverse('zds.tutorial.views.add_extract') + '?chapitre={}'.format(chapter.pk), + { + 'title': u"Extrait 1", + 'text': u"Introduisons notre premier extrait", + }, + follow=False) + self.assertEqual(result.status_code, 302) + self.assertEqual(Extract.objects.filter(chapter=chapter).count(), 1) + e1 = Extract.objects.filter(chapter=chapter).last() + + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_tutorial', + args=[ + tuto.pk, + tuto.slug]), + follow=True) + self.assertContains(response=result, text = u"Extrait 1") + self.assertContains(response=result, text = u"Introduisons notre premier extrait") + + #add extract 2 + result = self.client.post( + reverse('zds.tutorial.views.add_extract') + '?chapitre={}'.format(chapter.pk), + { + 'title': u"Extrait 2", + 'text': u"Introduisons notre deuxième extrait", + }, + follow=False) + self.assertEqual(result.status_code, 302) + self.assertEqual(Extract.objects.filter(chapter=chapter).count(), 2) + e2 = Extract.objects.filter(chapter=chapter).last() + + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_tutorial', + args=[ + tuto.pk, + tuto.slug]), + follow=True) + self.assertContains(response=result, text = u"Extrait 2") + self.assertContains(response=result, text = u"Introduisons notre deuxième extrait") + + #add extract 3 + result = self.client.post( + reverse('zds.tutorial.views.add_extract') + '?chapitre={}'.format(chapter.pk), + { + 'title': u"Extrait 3", + 'text': u"Introduisons notre troisième extrait", + }, + follow=False) + self.assertEqual(result.status_code, 302) + self.assertEqual(Extract.objects.filter(chapter=chapter).count(), 3) + e3 = Extract.objects.filter(chapter=chapter).last() + + #check view offline + result = self.client.get( + reverse( + 'zds.tutorial.views.view_tutorial', + args=[ + tuto.pk, + tuto.slug]), + follow=True) + self.assertContains(response=result, text = u"Extrait 3") + self.assertContains(response=result, text = u"Introduisons notre troisième extrait") + + #edit extract 2 + result = self.client.post( + reverse('zds.tutorial.views.edit_extract') + '?extrait={}'.format(e2.pk), + { + 'title': u"Extrait 2 : edition de titre", + 'text': u"Edition d'introduction", + }, + follow=True) + self.assertEqual(result.status_code, 200) + self.assertContains(response=result, text = u"Extrait 2 : edition de titre") + self.assertContains(response=result, text = u"Edition d'introduction") + self.assertEqual(Extract.objects.filter(chapter__tutorial=tuto).count(), 3) + + #move extract 1 against 2 + result = self.client.post( + reverse('zds.tutorial.views.modify_extract'), + { + 'extract': e1.pk, + 'move_target': e2.position_in_chapter, + }, + follow=True) + + #delete extract 1 + result = self.client.post( + reverse('zds.tutorial.views.modify_extract'), + { + 'extract': e1.pk, + 'delete': "OK", + }, + follow=True) + + self.assertEqual(Extract.objects.filter(chapter__tutorial=tuto).count(), 2) + def tearDown(self): if os.path.isdir(settings.REPO_PATH): shutil.rmtree(settings.REPO_PATH) diff --git a/zds/tutorial/urls.py b/zds/tutorial/urls.py index 55787d0c5b..049e4ebd68 100644 --- a/zds/tutorial/urls.py +++ b/zds/tutorial/urls.py @@ -9,7 +9,7 @@ # Viewing # Current URLs - url(r'^recherche/(?P<pk_user>.+)/$', + url(r'^recherche/(?P<pk_user>\d+)/$', 'zds.tutorial.views.find_tuto'), url(r'^off/(?P<tutorial_pk>\d+)/(?P<tutorial_slug>.+)/(?P<part_pk>\d+)/(?P<part_slug>.+)/(?P<chapter_pk>\d+)/(?P<chapter_slug>.+)/$', diff --git a/zds/tutorial/views.py b/zds/tutorial/views.py index b010edfc18..aa7d672122 100644 --- a/zds/tutorial/views.py +++ b/zds/tutorial/views.py @@ -73,14 +73,13 @@ def index(request): .order_by("-pubdate") \ .all() else: - # The tag isn't None and exist in the system. We can use it to retrieve # all tutorials in the subcategory specified. tutorials = Tutorial.objects.filter( sha_public__isnull=False, subcategory__in=[tag]).exclude(sha_public="").order_by("-pubdate").all() - return render_template("tutorial/index.html", {"tutorials": tutorials}) + return render_template("tutorial/index.html", {"tutorials": tutorials, "tag": tag}) # Staff actions. @@ -290,11 +289,14 @@ def reject_tutorial(request): u'certaines corrections peuvent surement être faite pour ' u'l’améliorer et repasser la validation plus tard. ' u'Voici le message que [{2}]({3}), ton validateur t\'a laissé `{4}`' - u'N\'hésite pas a lui envoyer un petit message pour discuter ' - u'de la décision ou demander plus de détail si tout cela te ' - u'semble injuste ou manque de clarté.' - .format(author.username, tutorial.title, validation.validator.username, - settings.SITE_URL + validation.validator.profile.get_absolute_url(), validation.comment_validator)) + u'N\'hésite pas à lui envoyer un petit message pour discuter ' + u'de la décision ou demander plus de détails si tout cela te ' + u'semble injuste ou manque de clarté.'.format( + author.username, + tutorial.title, + validation.validator.username, + settings.SITE_URL + validation.validator.profile.get_absolute_url(), + validation.comment_validator)) bot = get_object_or_404(User, username=settings.BOT_ACCOUNT) send_mp( bot, @@ -345,7 +347,7 @@ def valid_tutorial(request): # Update sha_public with the sha of validation. We don't update sha_draft. # So, the user can continue to edit his tutorial in offline. - if request.POST.get('is_major', False) or tutorial.sha_public is None: + if request.POST.get('is_major', False) or tutorial.sha_public is None or tutorial.sha_public == '': tutorial.pubdate = datetime.now() tutorial.sha_public = validation.version tutorial.source = request.POST["source"] @@ -359,16 +361,16 @@ def valid_tutorial(request): msg = ( u'Félicitations **{0}** ! Ton zeste [{1}]({2}) ' u'a été publié par [{3}]({4}) ! Les lecteurs du monde entier ' - u'peuvent venir l\'éplucher et réagir a son sujet. ' - u'Je te conseille de rester a leur écoute afin ' + u'peuvent venir l\'éplucher et réagir à son sujet. ' + u'Je te conseille de rester à leur écoute afin ' u'd\'apporter des corrections/compléments.' - u'Un Tutoriel vivant et a jour est bien plus lu ' - u'qu\'un sujet abandonné !' - .format(author.username, - tutorial.title, - settings.SITE_URL + tutorial.get_absolute_url_online(), - validation.validator.username, - settings.SITE_URL + validation.validator.profile.get_absolute_url())) + u'Un Tutoriel vivant et à jour est bien plus lu ' + u'qu\'un sujet abandonné !'.format( + author.username, + tutorial.title, + settings.SITE_URL + tutorial.get_absolute_url_online(), + validation.validator.username, + settings.SITE_URL + validation.validator.profile.get_absolute_url())) bot = get_object_or_404(User, username=settings.BOT_ACCOUNT) send_mp( bot, @@ -442,8 +444,13 @@ def ask_validation(request): if not request.user.has_perm("tutorial.change_tutorial"): raise PermissionDenied + #delete old pending validation + Validation.objects.filter(tutorial__pk=tutorial_pk, + status__in=['PENDING','PENDING_V'])\ + .delete() # We create and save validation object of the tutorial. - + + validation = Validation() validation.tutorial = tutorial validation.date_proposition = datetime.now() @@ -630,11 +637,6 @@ def view_tutorial(request, tutorial_pk, tutorial_slug): if not request.user.has_perm("tutorial.change_tutorial"): raise PermissionDenied - # Make sure the URL is well-formed - - if not tutorial_slug == slugify(tutorial.title): - return redirect(tutorial.get_absolute_url()) - # Two variables to handle two distinct cases (large/small tutorial) @@ -701,10 +703,11 @@ def view_tutorial(request, tutorial_pk, tutorial_slug): version=sha)\ .order_by("-date_proposition")\ .first() - formAskValidation = AskValidationForm() if tutorial.source: + formAskValidation = AskValidationForm(initial={"source": tutorial.source}) formValid = ValidForm(initial={"source": tutorial.source}) else: + formAskValidation = AskValidationForm() formValid = ValidForm() formReject = RejectForm() return render_template("tutorial/tutorial/view.html", { @@ -726,9 +729,8 @@ def view_tutorial_online(request, tutorial_pk, tutorial_slug): tutorial = get_object_or_404(Tutorial, pk=tutorial_pk) # If the tutorial isn't online, we raise 404 error. - - if not tutorial.on_line: - raise PermissionDenied + if not tutorial.on_line(): + raise Http404 # Two variables to handle two distinct cases (large/small tutorial) @@ -738,8 +740,9 @@ def view_tutorial_online(request, tutorial_pk, tutorial_slug): # find the good manifest file mandata = tutorial.load_json_for_public() - mandata = tutorial.load_dic(mandata) + mandata["update"] = tutorial.update + mandata["get_note_count"] = tutorial.get_note_count() # If it's a small tutorial, fetch its chapter @@ -981,6 +984,13 @@ def edit_tutorial(request): tutorial.update = datetime.now() + # MAJ gallery + + gal = Gallery.objects.filter(pk=tutorial.gallery.pk) + gal.update(title=data["title"]) + gal.update(slug=slugify(data["title"])) + gal.update(update=datetime.now()) + # MAJ image if "image" in request.FILES: @@ -1065,9 +1075,11 @@ def view_part( manifest = get_blob(repo.commit(sha).tree, "manifest.json") mandata = json_reader.loads(manifest) parts = mandata["parts"] + find = False cpt_p = 1 for part in parts: if part_pk == str(part["pk"]): + find = True part["tutorial"] = tutorial part["path"] = tutorial.get_path() part["slug"] = slugify(part["title"]) @@ -1094,7 +1106,11 @@ def view_part( final_part = part break cpt_p += 1 - + + # if part can't find + if not find: + raise Http404 + return render_template("tutorial/part/view.html", {"tutorial": tutorial, "part": final_part, @@ -1111,24 +1127,28 @@ def view_part_online( ): """Display a part.""" - part = get_object_or_404(Part, slug=part_slug, tutorial__pk=tutorial_pk) - tutorial = part.tutorial - if not tutorial.on_line: + tutorial = get_object_or_404(Tutorial, pk=tutorial_pk) + if not tutorial.on_line(): raise Http404 # find the good manifest file mandata = tutorial.load_json_for_public() mandata = tutorial.load_dic(mandata) + mandata["update"] = tutorial.update + mandata["get_parts"] = mandata["parts"] parts = mandata["parts"] cpt_p = 1 + final_part= None + find = False for part in parts: part["tutorial"] = mandata part["path"] = tutorial.get_path() part["slug"] = slugify(part["title"]) part["position_in_tutorial"] = cpt_p if part_pk == str(part["pk"]): + find = True intro = open(os.path.join(tutorial.get_prod_path(), part["introduction"] + ".html"), "r") part["intro"] = intro.read() @@ -1137,6 +1157,7 @@ def view_part_online( part["conclusion"] + ".html"), "r") part["conclu"] = conclu.read() conclu.close() + final_part=part cpt_c = 1 for chapter in part["chapters"]: chapter["part"] = part @@ -1156,7 +1177,11 @@ def view_part_online( part["get_chapters"] = part["chapters"] cpt_p += 1 - return render_template("tutorial/part/view_online.html", {"part": part}) + # if part can't find + if not find: + raise Http404 + + return render_template("tutorial/part/view_online.html", {"part": final_part}) @can_write_and_read_now @@ -1177,8 +1202,8 @@ def add_part(request): # Make sure the user belongs to the author list - if request.user not in tutorial.authors.all(): - raise Http404 + if request.user not in tutorial.authors.all() and not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied if request.method == "POST": form = PartForm(request.POST) if form.is_valid(): @@ -1227,8 +1252,8 @@ def modify_part(request): # Make sure the user is allowed to do that - if request.user not in part.tutorial.authors.all(): - raise Http404 + if request.user not in part.tutorial.authors.all() and not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied if "move" in request.POST: try: new_pos = int(request.POST["move_target"]) @@ -1247,7 +1272,6 @@ def modify_part(request): tuto = part.tutorial, action = "maj") elif "delete" in request.POST: - # Delete all chapters belonging to the part Chapter.objects.all().filter(part=part).delete() @@ -1260,11 +1284,18 @@ def modify_part(request): tut_p.position_in_tutorial = tut_p.position_in_tutorial - 1 tut_p.save() old_slug = os.path.join(settings.REPO_PATH, part.tutorial.get_phy_slug(), part.get_phy_slug()) - maj_repo_part(request, old_slug_path=old_slug, action="del") + maj_repo_part(request, old_slug_path=old_slug, part=part, action="del") + new_slug_tuto_path = os.path.join(settings.REPO_PATH, part.tutorial.get_phy_slug()) # Actually delete the part - part.delete() + + + maj_repo_tuto(request, + old_slug_path = new_slug_tuto_path, + new_slug_path = new_slug_tuto_path, + tuto = part.tutorial, + action = "maj") return redirect(part.tutorial.get_absolute_url()) @@ -1281,8 +1312,8 @@ def edit_part(request): # Make sure the user is allowed to do that - if request.user not in part.tutorial.authors.all(): - raise Http404 + if request.user not in part.tutorial.authors.all() and not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied if request.method == "POST": form = PartForm(request.POST) if form.is_valid(): @@ -1353,11 +1384,6 @@ def view_chapter( if not request.user.has_perm("tutorial.change_tutorial"): raise PermissionDenied - if not tutorial_slug == slugify(tutorial.title) or not part_slug \ - == slugify(chapter.part.title) or not chapter_slug \ - == slugify(chapter.title): - return redirect(chapter.get_absolute_url()) - # find the good manifest file repo = Repo(tutorial.get_path()) @@ -1368,6 +1394,7 @@ def view_chapter( final_chapter = None chapter_tab = [] final_position = 0 + find = False for part in parts: cpt_c = 1 part["slug"] = slugify(part["title"]) @@ -1376,7 +1403,7 @@ def view_chapter( args=[ tutorial.pk, tutorial.slug, - part_pk, + part["pk"], part["slug"]]) part["tutorial"] = tutorial for chapter in part["chapters"]: @@ -1386,13 +1413,22 @@ def view_chapter( chapter["type"] = "BIG" chapter["position_in_part"] = cpt_c chapter["position_in_tutorial"] = cpt_c * cpt_p - chapter["get_absolute_url"] = part["get_absolute_url"] \ - + "{0}/".format(chapter["slug"]) + chapter["get_absolute_url"] = reverse( + "zds.tutorial.views.view_chapter", + args=[ + tutorial.pk, + tutorial.slug, + part["pk"], + part["slug"], + chapter["pk"], + chapter["slug"]]) if chapter_pk == str(chapter["pk"]): + find = True chapter["intro"] = get_blob(repo.commit(sha).tree, chapter["introduction"]) chapter["conclu"] = get_blob(repo.commit(sha).tree, chapter["conclusion"]) + cpt_e = 1 for ext in chapter["extracts"]: ext["chapter"] = chapter @@ -1406,11 +1442,14 @@ def view_chapter( final_position = len(chapter_tab) - 1 cpt_c += 1 cpt_p += 1 + + # if chapter can't find + if not find: + raise Http404 - prev_chapter = (chapter_tab[final_position - 1] if final_position - > 0 else None) - next_chapter = (chapter_tab[final_position + 1] if final_position + 1 - < len(chapter_tab) else None) + prev_chapter = (chapter_tab[final_position - 1] if final_position > 0 else None) + next_chapter = (chapter_tab[final_position + 1] if final_position + 1 < len(chapter_tab) else None) + return render_template("tutorial/chapter/view.html", { "tutorial": tutorial, "chapter": final_chapter, @@ -1432,24 +1471,24 @@ def view_chapter_online( ): """View chapter.""" - chapter_bd = get_object_or_404(Chapter, pk=chapter_pk, - part__pk=part_pk, - part__tutorial__pk=tutorial_pk) - - tutorial = chapter_bd.get_tutorial() - if not tutorial.on_line: + tutorial = get_object_or_404(Tutorial, pk=tutorial_pk) + if not tutorial.on_line(): raise Http404 # find the good manifest file mandata = tutorial.load_json_for_public() mandata = tutorial.load_dic(mandata) + mandata["update"] = tutorial.update + mandata['get_parts'] = mandata["parts"] parts = mandata["parts"] cpt_p = 1 final_chapter = None chapter_tab = [] final_position = 0 + + find = False for part in parts: cpt_c = 1 part["slug"] = slugify(part["title"]) @@ -1458,7 +1497,7 @@ def view_chapter_online( args=[ tutorial.pk, tutorial.slug, - part_pk, + part["pk"], part["slug"]]) part["tutorial"] = mandata part["position_in_tutorial"] = cpt_p @@ -1470,9 +1509,17 @@ def view_chapter_online( chapter["type"] = "BIG" chapter["position_in_part"] = cpt_c chapter["position_in_tutorial"] = cpt_c * cpt_p - chapter["get_absolute_url_online"] = part[ - "get_absolute_url_online"] + "{0}/".format(chapter["slug"]) + chapter["get_absolute_url_online"] = reverse( + "zds.tutorial.views.view_chapter_online", + args=[ + tutorial.pk, + tutorial.slug, + part["pk"], + part["slug"], + chapter["pk"], + chapter["slug"]]) if chapter_pk == str(chapter["pk"]): + find = True intro = open( os.path.join( tutorial.get_prod_path(), @@ -1508,6 +1555,10 @@ def view_chapter_online( final_position = len(chapter_tab) - 1 cpt_c += 1 cpt_p += 1 + + # if chapter can't find + if not find: + raise Http404 prev_chapter = (chapter_tab[final_position - 1] if final_position > 0 else None) next_chapter = (chapter_tab[final_position + 1] if final_position + 1 < len(chapter_tab) else None) @@ -1533,8 +1584,8 @@ def add_chapter(request): # Make sure the user is allowed to do that - if request.user not in part.tutorial.authors.all(): - raise Http404 + if request.user not in part.tutorial.authors.all() and not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied if request.method == "POST": form = ChapterForm(request.POST, request.FILES) if form.is_valid(): @@ -1612,8 +1663,8 @@ def modify_chapter(request): # Make sure the user is allowed to do that - if request.user not in chapter.get_tutorial().authors.all(): - raise Http404 + if request.user not in chapter.get_tutorial().authors.all() and not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied if "move" in data: try: new_pos = int(request.POST["move_target"]) @@ -1654,7 +1705,7 @@ def modify_chapter(request): old_slug_path=chapter.get_path(), action="del") # Then delete the chapter - + new_slug_path_part = os.path.join(settings.REPO_PATH, chapter.part.tutorial.get_phy_slug()) chapter.delete() # Update all the position_in_tutorial fields for the next chapters @@ -1663,6 +1714,12 @@ def modify_chapter(request): Chapter.objects.filter(position_in_tutorial__gt=old_tut_pos): tut_c.update_position_in_tutorial() tut_c.save() + + maj_repo_part(request, + old_slug_path = new_slug_path_part, + new_slug_path = new_slug_path_part, + part = chapter.part, + action = "maj") messages.info(request, u"Le chapitre a bien été supprimé.") return redirect(parent.get_absolute_url()) @@ -1685,9 +1742,10 @@ def edit_chapter(request): # Make sure the user is allowed to do that - if big and request.user not in chapter.part.tutorial.authors.all() \ - or small and request.user not in chapter.tutorial.authors.all(): - raise Http404 + if (big and request.user not in chapter.part.tutorial.authors.all() \ + or small and request.user not in chapter.tutorial.authors.all())\ + and not request.user.has_perm("tutorial.change_tutorial"): + raise PermissionDenied if request.method == "POST": if chapter.part: form = ChapterForm(request.POST, request.FILES) @@ -1701,6 +1759,7 @@ def edit_chapter(request): old_slug = chapter.get_path() chapter.save() + chapter.update_children() if chapter.part: if chapter.tutorial: @@ -1722,7 +1781,6 @@ def edit_chapter(request): img.pubdate = datetime.now() img.save() chapter.image = img - maj_repo_chapter( request, old_slug_path=old_slug, @@ -1809,7 +1867,6 @@ def add_extract(request): @login_required def edit_extract(request): """Edit extract.""" - try: extract_pk = request.GET["extrait"] except KeyError: @@ -1920,8 +1977,24 @@ def modify_extract(request): # Use path retrieve before and use it to create the new slug. old_slug = extract.get_path() + + if extract.chapter.tutorial: + new_slug_path_chapter = os.path.join(settings.REPO_PATH, + extract.chapter.tutorial.get_phy_slug()) + else: + new_slug_path_chapter = os.path.join(settings.REPO_PATH, + chapter.part.tutorial.get_phy_slug(), + chapter.part.get_phy_slug(), + chapter.get_phy_slug()) + maj_repo_extract(request, old_slug_path=old_slug, extract=extract, action="del") + + maj_repo_chapter(request, + old_slug_path = new_slug_path_chapter, + new_slug_path = new_slug_path_chapter, + chapter = chapter, + action = "maj") return redirect(chapter.get_absolute_url()) elif "move" in data: try: @@ -2557,13 +2630,12 @@ def download_markdown(request): """Download a markdown tutorial.""" tutorial = get_object_or_404(Tutorial, pk=request.GET["tutoriel"]) - response = HttpResponse( - open( - os.path.join( + phy_path = os.path.join( tutorial.get_prod_path(), tutorial.slug + - ".md"), - "rb").read(), + ".md") + response = HttpResponse( + open(phy_path, "rb").read(), mimetype="application/txt") response["Content-Disposition"] = \ "attachment; filename={0}.md".format(tutorial.slug) @@ -2575,13 +2647,14 @@ def download_html(request): """Download a pdf tutorial.""" tutorial = get_object_or_404(Tutorial, pk=request.GET["tutoriel"]) - response = HttpResponse( - open( - os.path.join( + phy_path = os.path.join( tutorial.get_prod_path(), tutorial.slug + - ".html"), - "rb").read(), + ".html") + if not os.path.isfile(phy_path): + raise Http404 + response = HttpResponse( + open(phy_path, "rb").read(), mimetype="text/html") response["Content-Disposition"] = \ "attachment; filename={0}.html".format(tutorial.slug) @@ -2593,13 +2666,14 @@ def download_pdf(request): """Download a pdf tutorial.""" tutorial = get_object_or_404(Tutorial, pk=request.GET["tutoriel"]) - response = HttpResponse( - open( - os.path.join( + phy_path = os.path.join( tutorial.get_prod_path(), tutorial.slug + - ".pdf"), - "rb").read(), + ".pdf") + if not os.path.isfile(phy_path): + raise Http404 + response = HttpResponse( + open(phy_path, "rb").read(), mimetype="application/pdf") response["Content-Disposition"] = \ "attachment; filename={0}.pdf".format(tutorial.slug) @@ -2611,13 +2685,14 @@ def download_epub(request): """Download an epub tutorial.""" tutorial = get_object_or_404(Tutorial, pk=request.GET["tutoriel"]) - response = HttpResponse( - open( - os.path.join( + phy_path = os.path.join( tutorial.get_prod_path(), tutorial.slug + - ".epub"), - "rb").read(), + ".epub") + if not os.path.isfile(phy_path): + raise Http404 + response = HttpResponse( + open(phy_path, "rb").read(), mimetype="application/epub") response["Content-Disposition"] = \ "attachment; filename={0}.epub".format(tutorial.slug) @@ -2664,18 +2739,19 @@ def get_url_images(md_text, pt): # relative link srcfile = settings.SITE_ROOT + img[1] - dstroot = pt + img[1] - dstdir = os.path.dirname(dstroot) - if not os.path.exists(dstdir): - os.makedirs(dstdir) - shutil.copy(srcfile, dstroot) - ext = dstroot.split(".")[-1] - - # if image is gif, convert to png - - if ext == "gif": - im = ImagePIL.open(dstroot) - im.save(os.path.join(dstroot.split(".")[0] + ".png")) + if os.path.isfile(srcfile): + dstroot = pt + img[1] + dstdir = os.path.dirname(dstroot) + if not os.path.exists(dstdir): + os.makedirs(dstdir) + shutil.copy(srcfile, dstroot) + ext = dstroot.split(".")[-1] + + # if image is gif, convert to png + + if ext == "gif": + im = ImagePIL.open(dstroot) + im.save(os.path.join(dstroot.split(".")[0] + ".png")) def sub_urlimg(g): @@ -2910,9 +2986,10 @@ def answer(request): raise PermissionDenied for line in note_cite.text.splitlines(): text = text + "> " + line + "\n" - text = u"{0}Source:[{1}]({2})".format( + text = u"{0}Source:[{1}]({2}{3})".format( text, note_cite.author.username, + settings.SITE_URL, note_cite.get_absolute_url()) form = NoteForm(tutorial, request.user, initial={"text": text}) return render_template("tutorial/comment/new.html", { @@ -3044,13 +3121,13 @@ def edit_note(request): @login_required def like_note(request): """Like a note.""" - try: note_pk = request.GET["message"] except KeyError: raise Http404 resp = {} note = get_object_or_404(Note, pk=note_pk) + user = request.user if note.author.pk != request.user.pk: diff --git a/zds/urls.py b/zds/urls.py index ac325bc9bd..0ac94d40b9 100644 --- a/zds/urls.py +++ b/zds/urls.py @@ -60,7 +60,7 @@ def location(self, article): priority=0.7 ), 'topics': GenericSitemap( - {'queryset': Topic.objects.filter(is_locked=False), 'date_field': 'pubdate'}, + {'queryset': Topic.objects.filter(is_locked=False, forum__group__isnull=True), 'date_field': 'pubdate'}, changefreq='hourly', priority=0.7 ), diff --git a/zds/utils/articles.py b/zds/utils/articles.py index 5f1cf45f31..ec1e176c0b 100644 --- a/zds/utils/articles.py +++ b/zds/utils/articles.py @@ -15,6 +15,8 @@ def export_article(article): dct['description'] = article.description dct['type'] = 'article' dct['text'] = article.text + if article.licence: + dct['licence'] = article.licence.code return dct diff --git a/zds/utils/migrations/0002_auto__add_field_alert_comment__add_field_alert_scope__chg_field_alert_.py b/zds/utils/migrations/0002_auto__add_field_alert_comment__add_field_alert_scope__chg_field_alert_.py index d65173ddc2..08be15c8bc 100644 --- a/zds/utils/migrations/0002_auto__add_field_alert_comment__add_field_alert_scope__chg_field_alert_.py +++ b/zds/utils/migrations/0002_auto__add_field_alert_comment__add_field_alert_scope__chg_field_alert_.py @@ -16,7 +16,7 @@ def forwards(self, orm): u'utils_alert', 'comment', self.gf('django.db.models.fields.related.ForeignKey')( - default=0, + default=1, related_name='comments', to=orm['utils.Comment']), keep_default=False)