From 0012338e022501d9fb6978efeff926a55ffbb94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fran=C3=A7ois=20dambrine?= Date: Fri, 18 Jul 2014 20:50:05 +0200 Subject: [PATCH 01/20] =?UTF-8?q?fix=20843=20:=20les=20conflits=20d'=C3=A9?= =?UTF-8?q?ditions=20simultan=C3=A9s=20donnent=20lieu=20=C3=A0=20un=20?= =?UTF-8?q?=C3=A9crasement=20de=20la=20version=20interm=C3=A9diaire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/tutorial/chapter/edit.html | 3 + templates/tutorial/extract/edit.html | 3 + templates/tutorial/part/edit.html | 3 + templates/tutorial/tutorial/edit.html | 5 ++ zds/tutorial/forms.py | 12 ++- zds/tutorial/views.py | 116 ++++++++++++++++---------- zds/utils/misc.py | 18 +++- zds/utils/tutorials.py | 3 +- 8 files changed, 116 insertions(+), 47 deletions(-) diff --git a/templates/tutorial/chapter/edit.html b/templates/tutorial/chapter/edit.html index c18be0a24a..696f8f3b76 100644 --- a/templates/tutorial/chapter/edit.html +++ b/templates/tutorial/chapter/edit.html @@ -40,5 +40,8 @@

{% block content %} + {% if new_version %} +

Une nouvelle version a été postée

+ {% endif%} {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/extract/edit.html b/templates/tutorial/extract/edit.html index 3969f9c6ef..f5b585d53a 100644 --- a/templates/tutorial/extract/edit.html +++ b/templates/tutorial/extract/edit.html @@ -29,6 +29,9 @@

Éditer l'extrait

{% block content %} + {% if new_version %} +

Une nouvelle version a été postée

+ {% endif%} {% crispy form %} {% if form.text.value %} diff --git a/templates/tutorial/part/edit.html b/templates/tutorial/part/edit.html index 421a8f826d..82dbb0257c 100644 --- a/templates/tutorial/part/edit.html +++ b/templates/tutorial/part/edit.html @@ -35,5 +35,8 @@

{% block content %} + {% if new_version %} +

Une nouvelle version a été postée

+ {% endif%} {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/tutorial/edit.html b/templates/tutorial/tutorial/edit.html index c14defe5f8..59bf338268 100644 --- a/templates/tutorial/tutorial/edit.html +++ b/templates/tutorial/tutorial/edit.html @@ -29,10 +29,15 @@

{% block breadcrumb %}
  • {{ tutorial.title }}
  • Éditer le tutoriel
  • + + {% endblock %} {% block content %} + {% if new_version %} +

    Une nouvelle version a été postée

    + {% endif%} {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/zds/tutorial/forms.py b/zds/tutorial/forms.py index b5a7ea2ff2..add0efbae6 100644 --- a/zds/tutorial/forms.py +++ b/zds/tutorial/forms.py @@ -108,6 +108,7 @@ def __init__(self, *args, **kwargs): Field('image'), Field('introduction', css_class='md-editor'), Field('conclusion', css_class='md-editor'), + Hidden('last_hash', '{{ last_hash }}'), Field('subcategory'), Field('licence'), ButtonHolder( @@ -153,6 +154,7 @@ def __init__(self, *args, **kwargs): Field('title'), Field('introduction', css_class='md-editor'), Field('conclusion', css_class='md-editor'), + Hidden('last_hash', '{{ last_hash }}'), ButtonHolder( StrictButton( 'Valider', @@ -200,6 +202,7 @@ def __init__(self, *args, **kwargs): Field('image'), Field('introduction', css_class='md-editor'), Field('conclusion', css_class='md-editor'), + Hidden('last_hash', '{{ last_hash }}'), ButtonHolder( StrictButton( 'Valider', @@ -236,7 +239,8 @@ def __init__(self, *args, **kwargs): u'Contenu', Field('image'), Field('introduction', css_class='md-editor'), - Field('conclusion', css_class='md-editor') + Field('conclusion', css_class='md-editor'), + Hidden('last_hash', '{{ last_hash }}'), ), ButtonHolder( Submit('submit', 'Valider') @@ -265,6 +269,7 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( Field('title'), + Hidden('last_hash', '{{ last_hash }}'), CommonLayoutEditor() ) @@ -387,8 +392,9 @@ 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/views.py b/zds/tutorial/views.py index ed7ca774b4..f3d02e60ac 100644 --- a/zds/tutorial/views.py +++ b/zds/tutorial/views.py @@ -54,8 +54,18 @@ from zds.utils.paginator import paginator_range from zds.utils.templatetags.emarkdown import emarkdown from zds.utils.tutorials import get_blob, export_tutorial_to_md, move +from zds.utils.misc import compute_hash, content_has_changed +def render_chapter_form(chapter): + if chapter.part: + return ChapterForm({"title": chapter.title, + "introduction": chapter.get_introduction(), + "conclusion": chapter.get_conclusion()}) + else: + return \ + EmbdedChapterForm({"introduction": chapter.get_introduction(), + "conclusion": chapter.get_conclusion()}) def index(request): """Display all public tutorials of the website.""" @@ -704,11 +714,10 @@ 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", { @@ -1295,7 +1304,8 @@ def edit_part(request): except KeyError: raise Http404 part = get_object_or_404(Part, pk=part_pk) - + introduction = os.path.join(part.get_path(), "introduction.md") + conclusion = os.path.join(part.get_path(), "conclusion.md") # Make sure the user is allowed to do that if request.user not in part.tutorial.authors.all() and not request.user.has_perm("tutorial.change_tutorial"): @@ -1304,7 +1314,18 @@ def edit_part(request): form = PartForm(request.POST) if form.is_valid(): data = form.data - + # avoid collision + if content_has_changed([introduction, conclusion],data["last_hash"]): + form = PartForm({"title": part.title, + "introduction": part.get_introduction(), + "conclusion": part.get_conclusion()}) + return render_template("tutorial/part/edit.html", + { + "part": part, + "last_hash": compute_hash([introduction, conclusion]), + "new_version":True, + "form": form + }) # Update title and his slug. part.title = data["title"] @@ -1333,8 +1354,12 @@ def edit_part(request): form = PartForm({"title": part.title, "introduction": part.get_introduction(), "conclusion": part.get_conclusion()}) - return render_template("tutorial/part/edit.html", {"part": part, - "form": form}) + return render_template("tutorial/part/edit.html", + { + "part": part, + "last_hash": compute_hash([introduction, conclusion]), + "form": form + }) # Chapters. @@ -1388,7 +1413,7 @@ def view_chapter( args=[ tutorial.pk, tutorial.slug, - part["pk"], + part_pk, part["slug"]]) part["tutorial"] = tutorial for chapter in part["chapters"]: @@ -1398,15 +1423,8 @@ def view_chapter( chapter["type"] = "BIG" chapter["position_in_part"] = cpt_c chapter["position_in_tutorial"] = cpt_c * cpt_p - chapter["get_absolute_url"] = reverse( - "zds.tutorial.views.view_chapter", - args=[ - tutorial.pk, - tutorial.slug, - part["pk"], - part["slug"], - chapter["pk"], - chapter["slug"]]) + chapter["get_absolute_url"] = part["get_absolute_url"] \ + + "{0}/{1}/".format(chapter["pk"], chapter["slug"]) if chapter_pk == str(chapter["pk"]): chapter["intro"] = get_blob(repo.commit(sha).tree, chapter["introduction"]) @@ -1427,8 +1445,10 @@ def view_chapter( cpt_c += 1 cpt_p += 1 - 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, @@ -1474,7 +1494,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 @@ -1486,15 +1506,8 @@ 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"] = reverse( - "zds.tutorial.views.view_chapter_online", - args=[ - tutorial.pk, - tutorial.slug, - part["pk"], - part["slug"], - chapter["pk"], - chapter["slug"]]) + chapter["get_absolute_url_online"] = part[ + "get_absolute_url_online"] + "{0}/{1}/".format(chapter["pk"], chapter["slug"]) if chapter_pk == str(chapter["pk"]): intro = open( os.path.join( @@ -1718,7 +1731,10 @@ def edit_chapter(request): or small and request.user not in chapter.tutorial.authors.all())\ and not request.user.has_perm("tutorial.change_tutorial"): raise PermissionDenied + introduction = os.path.join(chapter.get_path(), "introduction.md") + conclusion = os.path.join(chapter.get_path(), "conclusion.md") if request.method == "POST": + if chapter.part: form = ChapterForm(request.POST, request.FILES) gal = chapter.part.tutorial.gallery @@ -1727,6 +1743,16 @@ def edit_chapter(request): gal = chapter.tutorial.gallery if form.is_valid(): data = form.data + # avoid collision + if content_has_changed([introduction, conclusion], data["last_hash"]): + form = render_chapter_form(chapter) + return render_template("tutorial/part/edit.html", + { + "chapter": chapter, + "last_hash": compute_hash([introduction, conclusion]), + "new_version":True, + "form": form + }) chapter.title = data["title"] old_slug = chapter.get_path() @@ -1764,16 +1790,9 @@ def edit_chapter(request): ) return redirect(chapter.get_absolute_url()) else: - if chapter.part: - form = ChapterForm({"title": chapter.title, - "introduction": chapter.get_introduction(), - "conclusion": chapter.get_conclusion()}) - else: - - form = \ - EmbdedChapterForm({"introduction": chapter.get_introduction(), - "conclusion": chapter.get_conclusion()}) + form = render_chapter_form(chapter) return render_template("tutorial/chapter/edit.html", {"chapter": chapter, + "last_hash": compute_hash([introduction, conclusion]), "form": form}) @@ -1860,7 +1879,17 @@ def edit_extract(request): raise PermissionDenied if request.method == "POST": data = request.POST - + if content_has_changed([extract.get_path()], data["last_hash"]): + form = form = ExtractForm(initial={ + "title": extract.title, + "text": extract.get_text()}) + return render_template("tutorial/extract/edit.html", + { + "extract": extract, + "last_hash": compute_hash([extract.get_path()]), + "new_version":True, + "form": form + }) # Using the « preview button » if "preview" in data: @@ -1909,8 +1938,12 @@ def edit_extract(request): else: form = ExtractForm({"title": extract.title, "text": extract.get_text()}) - return render_template("tutorial/extract/edit.html", {"extract": extract, - "form": form}) + return render_template("tutorial/extract/edit.html", + { + "extract": extract, + "last_hash": compute_hash([extract.get_path()]), + "form": form + }) @can_write_and_read_now @@ -2958,10 +2991,9 @@ def answer(request): raise PermissionDenied for line in note_cite.text.splitlines(): text = text + "> " + line + "\n" - text = u"{0}Source:[{1}]({2}{3})".format( + text = u"{0}Source:[{1}]({2})".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", { diff --git a/zds/utils/misc.py b/zds/utils/misc.py index 21a01718e0..f3ddea168e 100644 --- a/zds/utils/misc.py +++ b/zds/utils/misc.py @@ -3,7 +3,7 @@ import os import string import uuid - +import hashlib THUMB_MAX_WIDTH = 80 THUMB_MAX_HEIGHT = 80 @@ -11,6 +11,22 @@ MEDIUM_MAX_WIDTH = 200 MEDIUM_MAX_HEIGHT = 200 +def compute_hash(filenames): + """returns a md5 hexdigest of group of files to check if they have change""" + md5_hash = hashlib.md5() + for filename in filenames: + file_handle = open(filename, 'rb') + must_continue = True + while must_continue: + read_bytes = file_handle.read(8096) + if not read_bytes: + must_continue = False + else: + md5_hash.update(read_bytes) + return md5_hash.hexdigest() + +def content_has_changed(filenames, md5): + return md5 != compute_hash(filenames) def image_path(instance, filename): """Return path to an image.""" diff --git a/zds/utils/tutorials.py b/zds/utils/tutorials.py index acf84fda96..fa53615985 100644 --- a/zds/utils/tutorials.py +++ b/zds/utils/tutorials.py @@ -9,7 +9,6 @@ from zds.utils import slugify - # Export-to-dict functions def export_chapter(chapter, export_all=True): from zds.tutorial.models import Extract @@ -289,3 +288,5 @@ def move(obj, new_pos, position_f, parent_f, children_fn): # All objects have been updated except the current one we want to move, so # we can do it now setattr(obj, position_f, new_pos) + + From 8a9118b442db2d7ace67efc3a573929a5e487e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fran=C3=A7ois=20dambrine?= Date: Fri, 18 Jul 2014 21:37:47 +0200 Subject: [PATCH 02/20] =?UTF-8?q?mise=20=C3=A0=20jour=20des=20tests=20unit?= =?UTF-8?q?aires?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zds/tutorial/tests.py | 52 +++++-------------------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/zds/tutorial/tests.py b/zds/tutorial/tests.py index 442a91598f..110e49b89e 100644 --- a/zds/tutorial/tests.py +++ b/zds/tutorial/tests.py @@ -706,6 +706,7 @@ def test_workflow_tuto(self): 'title': u"Partie 2 : edition de titre", 'introduction': u"Expérimentation : edition d'introduction", 'conclusion': u"C'est terminé : edition de conlusion", + "last_hash": compute_hash([os.path.join(p2.get_path(),"introduction.md"),os.path.join(p2.get_path(),"conclusion.md")]) }, follow=True) self.assertContains(response=result, text = u"Partie 2 : edition de titre") @@ -720,6 +721,7 @@ def test_workflow_tuto(self): 'title': u"Chapitre 3 : edition de titre", 'introduction': u"Edition d'introduction", 'conclusion': u"Edition de conlusion", + "last_hash": compute_hash([os.path.join(c3.get_path(),"introduction.md"),os.path.join(c3.get_path(),"conclusion.md")]) }, follow=True) self.assertContains(response=result, text = u"Chapitre 3 : edition de titre") @@ -734,6 +736,7 @@ def test_workflow_tuto(self): '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", + "last_hash": compute_hash([os.path.join(p2.get_path(),"introduction.md"),os.path.join(p2.get_path(),"conclusion.md")]) }, follow=True) self.assertContains(response=result, text = u"Partie 2 : seconde edition de titre") @@ -748,6 +751,7 @@ def test_workflow_tuto(self): 'title': u"Chapitre 2 : edition de titre", 'introduction': u"Edition d'introduction", 'conclusion': u"Edition de conlusion", + "last_hash": compute_hash([os.path.join(c2.get_path(),"introduction.md"),os.path.join(c2.get_path(),"conclusion.md")]) }, follow=True) self.assertContains(response=result, text = u"Chapitre 2 : edition de titre") @@ -792,53 +796,6 @@ def test_workflow_tuto(self): self.assertEqual(Chapter.objects.filter(part__tutorial=tuto.pk).count(), 2) self.assertEqual(Part.objects.filter(tutorial=tuto.pk).count(), 2) - 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.""" @@ -2411,6 +2368,7 @@ def test_workflow_tuto(self): { 'title': u"Extrait 2 : edition de titre", 'text': u"Edition d'introduction", + "last_hash": compute_hash([e2.get_path(), e2.get_path()]) }, follow=True) self.assertEqual(result.status_code, 200) From a28c9dd4f3020cf78f3d9668535a0d6e38a5c359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fran=C3=A7ois=20dambrine?= Date: Fri, 18 Jul 2014 22:06:55 +0200 Subject: [PATCH 03/20] =?UTF-8?q?derniers=20probl=C3=A8mes=20d'actualisati?= =?UTF-8?q?on=20des=20TU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zds/tutorial/tests.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/zds/tutorial/tests.py b/zds/tutorial/tests.py index 110e49b89e..b83b02d4ca 100644 --- a/zds/tutorial/tests.py +++ b/zds/tutorial/tests.py @@ -18,7 +18,7 @@ from zds.gallery.factories import GalleryFactory from zds.tutorial.models import Note, Tutorial, Validation, Extract, Part, Chapter from zds.utils.models import SubCategory, Licence, Alert - +from zds.utils.misc import compute_hash @override_settings(MEDIA_ROOT=os.path.join(SITE_ROOT, 'media-test')) @override_settings(REPO_PATH=os.path.join(SITE_ROOT, 'tutoriels-private-test')) @@ -540,7 +540,7 @@ def test_workflow_tuto(self): self.assertEqual(result.status_code, 302) self.assertEqual(Part.objects.filter(tutorial=tuto).count(), 2) p2 = Part.objects.filter(tutorial=tuto).last() - + self.assertEqual(u"Analyse", p2.get_introduction()) #check view offline result = self.client.get( reverse( @@ -706,7 +706,8 @@ def test_workflow_tuto(self): 'title': u"Partie 2 : edition de titre", 'introduction': u"Expérimentation : edition d'introduction", 'conclusion': u"C'est terminé : edition de conlusion", - "last_hash": compute_hash([os.path.join(p2.get_path(),"introduction.md"),os.path.join(p2.get_path(),"conclusion.md")]) + "last_hash": compute_hash([os.path.join(p2.tutorial.get_path(), p2.introduction), + os.path.join(p2.tutorial.get_path(), p2.conclusion)]) }, follow=True) self.assertContains(response=result, text = u"Partie 2 : edition de titre") @@ -728,7 +729,7 @@ def test_workflow_tuto(self): 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) - + p2 = Part.objects.filter(pk=p2.pk).first() #edit part 2 result = self.client.post( reverse('zds.tutorial.views.edit_part') + '?partie={}'.format(p2.pk), @@ -736,7 +737,8 @@ def test_workflow_tuto(self): '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", - "last_hash": compute_hash([os.path.join(p2.get_path(),"introduction.md"),os.path.join(p2.get_path(),"conclusion.md")]) + "last_hash": compute_hash([os.path.join(p2.tutorial.get_path(), p2.introduction), + os.path.join(p2.tutorial.get_path(), p2.conclusion)]) }, follow=True) self.assertContains(response=result, text = u"Partie 2 : seconde edition de titre") @@ -2368,7 +2370,7 @@ def test_workflow_tuto(self): { 'title': u"Extrait 2 : edition de titre", 'text': u"Edition d'introduction", - "last_hash": compute_hash([e2.get_path(), e2.get_path()]) + "last_hash": compute_hash([e2.get_path()]) }, follow=True) self.assertEqual(result.status_code, 200) From b34e5a0cb06d869401b34e3ef684a460ee0c29ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fran=C3=A7ois=20dambrine?= Date: Sat, 19 Jul 2014 13:23:48 +0200 Subject: [PATCH 04/20] =?UTF-8?q?l'accueil=20des=20tuto=20est=20aussi=20pr?= =?UTF-8?q?ot=C3=A9g=C3=A9e=20contre=20l'=C3=A9crasement,=20l'avertissemen?= =?UTF-8?q?t=20est=20d=C3=A9sormais=20mieux=20int=C3=A9gr=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ajoute des tests unitaires pep8 et indentation dans les templates --- templates/tutorial/chapter/edit.html | 4 +- templates/tutorial/extract/edit.html | 4 +- templates/tutorial/part/edit.html | 6 +- templates/tutorial/tutorial/edit.html | 13 ++- zds/tutorial/tests.py | 118 +++++++++++++++++++++++++- zds/tutorial/views.py | 21 ++++- 6 files changed, 151 insertions(+), 15 deletions(-) diff --git a/templates/tutorial/chapter/edit.html b/templates/tutorial/chapter/edit.html index 696f8f3b76..8bef5a9fba 100644 --- a/templates/tutorial/chapter/edit.html +++ b/templates/tutorial/chapter/edit.html @@ -41,7 +41,9 @@

    {% block content %} {% if new_version %} -

    Une nouvelle version a été postée

    +

    + Une nouvelle version a été postée avant que vous ne validiez. +

    {% endif%} {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/extract/edit.html b/templates/tutorial/extract/edit.html index f5b585d53a..05be5a735e 100644 --- a/templates/tutorial/extract/edit.html +++ b/templates/tutorial/extract/edit.html @@ -30,7 +30,9 @@

    Éditer l'extrait

    {% block content %} {% if new_version %} -

    Une nouvelle version a été postée

    +

    + Une nouvelle version a été postée avant que vous ne validiez. +

    {% endif%} {% crispy form %} diff --git a/templates/tutorial/part/edit.html b/templates/tutorial/part/edit.html index 82dbb0257c..ef9eff34ad 100644 --- a/templates/tutorial/part/edit.html +++ b/templates/tutorial/part/edit.html @@ -36,7 +36,9 @@

    {% block content %} {% if new_version %} -

    Une nouvelle version a été postée

    - {% endif%} +

    + Une nouvelle version a été postée avant que vous ne validiez. +

    + {% endif %} {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/tutorial/edit.html b/templates/tutorial/tutorial/edit.html index 59bf338268..a5e35ee61a 100644 --- a/templates/tutorial/tutorial/edit.html +++ b/templates/tutorial/tutorial/edit.html @@ -21,23 +21,20 @@

    {% block headline_sub %} - {{ tutorial.description }} + {{ tutorial.description }} {% endblock %} - - - {% block breadcrumb %}
  • {{ tutorial.title }}
  • Éditer le tutoriel
  • - - {% endblock %} {% block content %} {% if new_version %} -

    Une nouvelle version a été postée

    - {% endif%} +

    + Une nouvelle version a été postée avant que vous ne validiez. +

    + {% endif %} {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/zds/tutorial/tests.py b/zds/tutorial/tests.py index b83b02d4ca..09181b3754 100644 --- a/zds/tutorial/tests.py +++ b/zds/tutorial/tests.py @@ -722,7 +722,8 @@ def test_workflow_tuto(self): 'title': u"Chapitre 3 : edition de titre", 'introduction': u"Edition d'introduction", 'conclusion': u"Edition de conlusion", - "last_hash": compute_hash([os.path.join(c3.get_path(),"introduction.md"),os.path.join(c3.get_path(),"conclusion.md")]) + "last_hash": compute_hash([os.path.join(c3.get_path(),"introduction.md"), + os.path.join(c3.get_path(),"conclusion.md")]) }, follow=True) self.assertContains(response=result, text = u"Chapitre 3 : edition de titre") @@ -753,7 +754,8 @@ def test_workflow_tuto(self): 'title': u"Chapitre 2 : edition de titre", 'introduction': u"Edition d'introduction", 'conclusion': u"Edition de conlusion", - "last_hash": compute_hash([os.path.join(c2.get_path(),"introduction.md"),os.path.join(c2.get_path(),"conclusion.md")]) + "last_hash": compute_hash([os.path.join(c2.get_path(),"introduction.md"), + os.path.join(c2.get_path(),"conclusion.md")]) }, follow=True) self.assertContains(response=result, text = u"Chapitre 2 : edition de titre") @@ -798,6 +800,112 @@ def test_workflow_tuto(self): self.assertEqual(Chapter.objects.filter(part__tutorial=tuto.pk).count(), 2) self.assertEqual(Part.objects.filter(tutorial=tuto.pk).count(), 2) + def test_conflict_does_not_destroy(self): + """tests that simultaneous edition does not conflict""" + sub = SubCategory() + sub.title = "toto" + sub.save() + # logout before + self.client.logout() + # first, login with author : + self.assertEqual( + self.client.login( + username=self.user_author.username, + password='hostel77'), + True) + # test tuto + (introduction_path, conclusion_path) =(os.path.join(self.bigtuto.get_path(),"introduction.md"), os.path.join(self.bigtuto.get_path(),"conclusion.md")) + hash = compute_hash([introduction_path, conclusion_path]) + self.client.post( + reverse('zds.tutorial.views.edit_tutorial')+'?tutoriel={0}'.format(self.bigtuto.pk), + { + 'title': self.bigtuto.title, + 'description': "nouvelle description", + 'subcategory': [sub.pk], + 'introduction': self.bigtuto.get_introduction() +" un essai", + 'conclusion': self.bigtuto.get_conclusion(), + 'last_hash': hash + }, follow= True) + conflict_result = self.client.post( + reverse('zds.tutorial.views.edit_tutorial')+'?tutoriel={0}'.format(self.bigtuto.pk), + { + 'title': self.bigtuto.title, + 'description': "nouvelle description", + 'subcategory': [sub.pk], + 'introduction': self.bigtuto.get_introduction() +" conflictual", + 'conclusion': self.bigtuto.get_conclusion(), + 'last_hash': hash + }, follow= False) + self.assertEqual(conflict_result.status_code, 200) + self.assertContains(response=conflict_result, text = u"nouvelle version") + + # test parts + + result = self.client.post( + reverse('zds.tutorial.views.add_part') + '?tutoriel={}'.format(self.bigtuto.pk), + { + 'title': u"Partie 2", + 'introduction': u"Analyse", + 'conclusion': u"Fin de l'analyse", + }, + follow=False) + p1 = Part.objects.last() + hash = compute_hash([os.path.join(p1.tutorial.get_path(), p1.introduction), + os.path.join(p1.tutorial.get_path(), p1.conclusion)]) + self.client.post( + reverse('zds.tutorial.views.edit_part') + '?partie={}'.format(p1.pk), + { + 'title': u"Partie 2 : edition de titre", + 'introduction': u"Expérimentation : edition d'introduction", + 'conclusion': u"C'est terminé : edition de conlusion", + "last_hash": hash + }, + follow=False) + conflict_result = self.client.post( + reverse('zds.tutorial.views.edit_part') + '?partie={}'.format(p1.pk), + { + 'title': u"Partie 2 : edition de titre", + 'introduction': u"Expérimentation : edition d'introduction conflit", + 'conclusion': u"C'est terminé : edition de conlusion", + "last_hash": hash + }, + follow=False) + self.assertEqual(conflict_result.status_code, 200) + self.assertContains(response=conflict_result, text = u"nouvelle version") + + # test chapter + result = self.client.post( + reverse('zds.tutorial.views.add_chapter') + '?partie={}'.format(p1.pk), + { + 'title': u"Chapitre 1", + 'introduction':"Mon premier chapitre", + 'conclusion': "Fin de mon premier chapitre", + }, + follow=False) + c1 = Chapter.objects.last() + hash = compute_hash([os.path.join(c1.get_path(),"introduction.md"), + os.path.join(c1.get_path(),"conclusion.md")]) + self.client.post( + reverse('zds.tutorial.views.edit_chapter') + '?chapitre={}'.format(c1.pk), + { + 'title': u"Chapitre 3 : edition de titre", + 'introduction': u"Edition d'introduction", + 'conclusion': u"Edition de conlusion", + "last_hash": hash + }, + follow=True) + conflict_result = self.client.post( + reverse('zds.tutorial.views.edit_chapter') + '?chapitre={}'.format(c1.pk), + { + 'title': u"Chapitre 3 : edition de titre", + 'introduction': u"Edition d'introduction conflict", + 'conclusion': u"Edition de conlusion", + "last_hash": hash + }, + follow=True) + self.assertEqual(conflict_result.status_code, 200) + self.assertContains(response=conflict_result, text = u"nouvelle version") + def test_url_for_member(self): """Test simple get request by simple member.""" @@ -1337,6 +1445,8 @@ def test_gallery_tuto_change_name(self): 'introduction': self.bigtuto.introduction, 'description': self.bigtuto.description, 'conclusion': self.bigtuto.conclusion, + 'last_hash': compute_hash([os.path.join(self.bigtuto.get_path(),"introduction.md"), + os.path.join(self.bigtuto.get_path(),"conclusion.md")]) }, follow=True) @@ -2028,6 +2138,8 @@ def test_edit_tuto(self): 'subcategory': [sub.pk], 'introduction': self.minituto.get_introduction(), 'conclusion': self.minituto.get_conclusion(), + 'last_hash': compute_hash([os.path.join(self.minituto.get_path(),"introduction.md"), + os.path.join(self.minituto.get_path(),"conclusion.md")]) }, follow=False ) @@ -2045,6 +2157,8 @@ def test_edit_tuto(self): 'subcategory': [sub.pk], 'introduction': self.minituto.get_introduction(), 'conclusion': self.minituto.get_conclusion(), + 'last_hash': compute_hash([os.path.join(self.minituto.get_path(),"introduction.md"), + os.path.join(self.minituto.get_path(),"conclusion.md")]) }, follow=False ) diff --git a/zds/tutorial/views.py b/zds/tutorial/views.py index f3d02e60ac..b59f3f57b6 100644 --- a/zds/tutorial/views.py +++ b/zds/tutorial/views.py @@ -977,10 +977,29 @@ def edit_tutorial(request): if request.user not in tutorial.authors.all(): if not request.user.has_perm("tutorial.change_tutorial"): raise PermissionDenied + introduction = os.path.join(tutorial.get_path(), "introduction.md") + conclusion = os.path.join(tutorial.get_path(), "conclusion.md") if request.method == "POST": form = TutorialForm(request.POST, request.FILES) if form.is_valid(): data = form.data + if content_has_changed([introduction, conclusion], data["last_hash"]): + form = TutorialForm(initial={ + "title": tutorial.title, + "type": tutorial.type, + "licence": tutorial.licence, + "description": tutorial.description, + "subcategory": tutorial.subcategory.all(), + "introduction": tutorial.get_introduction(), + "conclusion": tutorial.get_conclusion(), + + }) + return render_template("tutorial/tutorial/edit.html", + { + "tutorial": tutorial, "form": form, + "last_hash": compute_hash([introduction, conclusion]), + "new_version": True + }) old_slug = tutorial.get_path() tutorial.title = data["title"] tutorial.description = data["description"] @@ -1045,7 +1064,7 @@ def edit_tutorial(request): "conclusion": tutorial.get_conclusion(), }) return render_template("tutorial/tutorial/edit.html", - {"tutorial": tutorial, "form": form}) + {"tutorial": tutorial, "form": form, "last_hash": compute_hash([introduction, conclusion])}) # Parts. From 240881fab2bc03a5a6836666192f87c95c005726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fran=C3=A7ois=20dambrine?= Date: Tue, 22 Jul 2014 07:28:50 +0200 Subject: [PATCH 05/20] indentation --- templates/tutorial/chapter/edit.html | 2 +- templates/tutorial/extract/edit.html | 2 +- templates/tutorial/tutorial/edit.html | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/tutorial/chapter/edit.html b/templates/tutorial/chapter/edit.html index 8bef5a9fba..3db72a8c76 100644 --- a/templates/tutorial/chapter/edit.html +++ b/templates/tutorial/chapter/edit.html @@ -44,6 +44,6 @@

    Une nouvelle version a été postée avant que vous ne validiez.

    - {% endif%} + {% endif %} {% crispy form %} {% endblock %} \ No newline at end of file diff --git a/templates/tutorial/extract/edit.html b/templates/tutorial/extract/edit.html index 05be5a735e..45341465d1 100644 --- a/templates/tutorial/extract/edit.html +++ b/templates/tutorial/extract/edit.html @@ -33,7 +33,7 @@

    Éditer l'extrait

    Une nouvelle version a été postée avant que vous ne validiez.

    - {% endif%} + {% endif %} {% crispy form %} {% if form.text.value %} diff --git a/templates/tutorial/tutorial/edit.html b/templates/tutorial/tutorial/edit.html index a5e35ee61a..6f3cdb358e 100644 --- a/templates/tutorial/tutorial/edit.html +++ b/templates/tutorial/tutorial/edit.html @@ -32,9 +32,9 @@

    {% block content %} {% if new_version %} -

    - Une nouvelle version a été postée avant que vous ne validiez. -

    +

    + Une nouvelle version a été postée avant que vous ne validiez. +

    {% endif %} {% crispy form %} {% endblock %} \ No newline at end of file From 85b15286b5d7f4449f8cf99758ce9df9b8f18bd1 Mon Sep 17 00:00:00 2001 From: Alex-D Date: Tue, 22 Jul 2014 16:29:31 +0200 Subject: [PATCH 06/20] =?UTF-8?q?Corrige=20la=20bani=C3=A8re=20cookies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/scss/_all-supports.scss | 53 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/assets/scss/_all-supports.scss b/assets/scss/_all-supports.scss index 7f59097dee..94893993e8 100644 --- a/assets/scss/_all-supports.scss +++ b/assets/scss/_all-supports.scss @@ -48,39 +48,41 @@ background: #062E41; display: none; - p { + span { + display: inline-block; margin: 0; padding: 7px 0; color: #EEE; + line-height: 23px; + } - a { - display: inline-block; - color: #EEE; - padding: 4px 13px; - margin-left: 15px; - background: $blue; - text-decoration: none; + a { + display: inline-block; + color: #EEE; + padding: 4px 13px; + margin-left: 15px; + background: $blue; + text-decoration: none; - &:hover, - &:focus { - background: #EEE; - color: $blue; - } + &:hover, + &:focus { + background: #EEE; + color: $blue; } + } - button { - display: inline-block; - background: none; - border: none; - text-decoration: underline; - margin: 0; - padding: 0; - color: #EEE; + #reject-cookies { + display: inline-block; + background: none; + border: none; + text-decoration: underline; + margin: 0; + padding: 0; + color: #EEE; - &:hover, - &:focus { - text-decoration: none; - } + &:hover, + &:focus { + text-decoration: none; } } @@ -91,6 +93,7 @@ padding: 4px 15px; border: none; transition: background .15s, color .15s; + margin-top: 3px; &:hover, &:focus { From 2e40a1ecffc0ce0ba0544aeb81e1c3cfd72d1c59 Mon Sep 17 00:00:00 2001 From: Alex-D Date: Tue, 22 Jul 2014 16:31:43 +0200 Subject: [PATCH 07/20] Fix #1257 Orthographe accueil --- templates/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/home.html b/templates/home.html index ca2ab9f2bc..9e71f19a7e 100644 --- a/templates/home.html +++ b/templates/home.html @@ -40,7 +40,7 @@

    Zeste de Savoir, la connaissance pour tous et sans pépins

    Tous les membres peuvent écrire et publier des tutoriels et articles sur le site. Pour assurer la qualité et la pédagogie du contenu, l'équipe du site valide chaque cours avant publication.

    - Tout cela est entièrement gratuit et garanti sans publicité, le site est géré et financé par une association a but non lucratif. + Tout cela est entièrement gratuit et garanti sans publicité, le site est géré et financé par une association à but non lucratif.

    From ed7b771e2e23140bf3cfadd36b2c81bd3c342738 Mon Sep 17 00:00:00 2001 From: Alex-D Date: Tue, 22 Jul 2014 16:44:36 +0200 Subject: [PATCH 08/20] =?UTF-8?q?Fix=20#1123=20block=20image=20>=20meta=5F?= =?UTF-8?q?image=20+=20metadonn=C3=A9es=20articles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/article/view.html | 25 ++++++++++++++++++++ templates/base.html | 10 ++++---- templates/tutorial/chapter/view_online.html | 16 +++++++++++++ templates/tutorial/part/view_online.html | 2 +- templates/tutorial/tutorial/view_online.html | 2 +- 5 files changed, 48 insertions(+), 7 deletions(-) diff --git a/templates/article/view.html b/templates/article/view.html index 13de5eb1b0..b1565dbc84 100644 --- a/templates/article/view.html +++ b/templates/article/view.html @@ -14,6 +14,31 @@ +{% block meta_image %}{% spaceless %} + {% if article.image %} + {{ article.image.article_illu.url }} + {% else %} + {{ block.super }} + {% endif %} +{% endspaceless %}{% endblock %} + + + +{% block opengraph %} + + + {% if article.pubdate %} + + {% endif %} + + + {% for tag in tags.all %} + + {% endfor %} +{% endblock %} + + + {% block breadcrumb %}
  • {{ article.title }}
  • {% endblock %} diff --git a/templates/base.html b/templates/base.html index bd057037e5..d201ef6668 100644 --- a/templates/base.html +++ b/templates/base.html @@ -45,8 +45,8 @@ - {% captureas image %} - {{ request.META.HTTP_HOST }}{% block image %}{% spaceless %} + {% captureas meta_image %} + {{ request.META.HTTP_HOST }}{% block meta_image %}{% spaceless %} {% static "images/apple-touch-icon-144x144-precomposed.png" %} {% endspaceless %}{% endblock %} {% endcaptureas %} @@ -57,8 +57,8 @@ - - + + {% block opengraph %} {% endblock %} @@ -72,7 +72,7 @@ - + {# Stylesheets #} diff --git a/templates/tutorial/chapter/view_online.html b/templates/tutorial/chapter/view_online.html index aee1ff44cb..a9f5dc7a36 100644 --- a/templates/tutorial/chapter/view_online.html +++ b/templates/tutorial/chapter/view_online.html @@ -9,6 +9,22 @@ +{% block meta_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 %}