From 1037e95dfaf6c3d41d3dd843572d19dc255f3af5 Mon Sep 17 00:00:00 2001 From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com> Date: Tue, 24 Sep 2024 21:56:05 +0200 Subject: [PATCH] =?UTF-8?q?Ajoute=20des=20formulaires=20d=C3=A9di=C3=A9s?= =?UTF-8?q?=20=C3=A0=20l'introduction=20et=20la=20conclusion=20(#6642)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/tutorialv2/edit/conclusion.html | 26 ++ .../edit/{content.html => introduction.html} | 10 +- .../includes/content/content.part.html | 10 +- templates/tutorialv2/view/content.html | 7 - zds/tutorialv2/forms.py | 50 +--- zds/tutorialv2/mixins.py | 2 +- zds/tutorialv2/tests/models/tests_models.py | 32 +- zds/tutorialv2/tests/tests_front.py | 39 +-- zds/tutorialv2/tests/tests_lists.py | 2 +- .../tests/tests_views/tests_content.py | 221 ++++---------- .../tests/tests_views/tests_display.py | 26 +- .../tests_views/tests_editconclusionview.py | 96 ++++++ .../tests_views/tests_editintroductionview.py | 96 ++++++ .../tests/tests_views/tests_published.py | 54 +--- zds/tutorialv2/urls/urls_contents.py | 10 +- zds/tutorialv2/utils.py | 2 +- zds/tutorialv2/views/contents.py | 282 +++++++++++++----- 17 files changed, 545 insertions(+), 420 deletions(-) create mode 100644 templates/tutorialv2/edit/conclusion.html rename templates/tutorialv2/edit/{content.html => introduction.html} (80%) create mode 100644 zds/tutorialv2/tests/tests_views/tests_editconclusionview.py create mode 100644 zds/tutorialv2/tests/tests_views/tests_editintroductionview.py diff --git a/templates/tutorialv2/edit/conclusion.html b/templates/tutorialv2/edit/conclusion.html new file mode 100644 index 0000000000..a82b24e4e5 --- /dev/null +++ b/templates/tutorialv2/edit/conclusion.html @@ -0,0 +1,26 @@ +{% extends "tutorialv2/base.html" %} +{% load crispy_forms_tags %} +{% load i18n %} + +{% block title %} + {% trans "Modifier la conclusion de " %}{{ content.title }} +{% endblock %} + +{% block breadcrumb %} +
  • {{ content.title }}
  • +
  • {% trans "Modifier la conclusion" %}
  • +{% endblock %} + +{% block headline %} +

    + {% if content.image %} + + {% endif %} + {% blocktrans with title=content.title %}Modifier la conclusion de « {{ title }} »{% endblocktrans %} +

    +{% endblock %} + + +{% block content %} + {% crispy form %} +{% endblock %} diff --git a/templates/tutorialv2/edit/content.html b/templates/tutorialv2/edit/introduction.html similarity index 80% rename from templates/tutorialv2/edit/content.html rename to templates/tutorialv2/edit/introduction.html index 21f42caf28..8162f3d7fc 100644 --- a/templates/tutorialv2/edit/content.html +++ b/templates/tutorialv2/edit/introduction.html @@ -5,12 +5,12 @@ {% load feminize %} {% block title %} - {% trans "Éditer " %}{{ content.textual_type }} + {% trans "Modifier l'introduction de " %}{{ content.title }} {% endblock %} {% block breadcrumb %}
  • {{ content.title }}
  • -
  • {% trans "Éditer " %}{{ content.textual_type|lower }}
  • +
  • {% trans "Modifier l'introduction" %}
  • {% endblock %} {% block headline %} @@ -18,14 +18,10 @@

    {% if content.image %} {% endif %} - {% trans "Éditer" %} : {{ content.title }} + {% blocktrans with title=content.title %}Modifier l'introduction de « {{ title }} »{% endblocktrans %}

    {% endblock %} -{% block headline_sub %} - {{ content.description }} -{% endblock %} - {% block content %} {% if new_version %} diff --git a/templates/tutorialv2/includes/content/content.part.html b/templates/tutorialv2/includes/content/content.part.html index 3e8ba4e0e0..d01bbd5805 100644 --- a/templates/tutorialv2/includes/content/content.part.html +++ b/templates/tutorialv2/includes/content/content.part.html @@ -4,7 +4,7 @@ {% if content.get_introduction %} {% if display_config.draft_actions.enable_edit %}
    - + {% trans "Modifier l'introduction" %}
    @@ -15,10 +15,12 @@

    {% trans "Il n’y a pas d’introduction." %} {% if display_config.draft_actions.enable_edit %} - {% trans "Vous pouvez " %}{% trans "en ajouter une" %}. + {% trans "Vous pouvez " %}{% trans "en ajouter une" %}. {% endif %}

    + +
    {% endif %} {% if content.has_extracts or content.can_add_extract %} @@ -108,7 +110,7 @@

    {% if content.get_conclusion %} {% if display_config.draft_actions.enable_edit %}
    - + {% trans "Modifier la conclusion" %}
    @@ -119,7 +121,7 @@

    {% trans "Il n’y a pas de conclusion." %} {% if display_config.draft_actions.enable_edit %} - {% trans "Vous pouvez " %}{% trans "en ajouter une" %}. + {% trans "Vous pouvez " %}{% trans "en ajouter une" %}. {% endif %}

    diff --git a/templates/tutorialv2/view/content.html b/templates/tutorialv2/view/content.html index 4f4b0c7462..bb197bdc86 100644 --- a/templates/tutorialv2/view/content.html +++ b/templates/tutorialv2/view/content.html @@ -170,13 +170,6 @@ {% endfor %} {% endif %} - {% if display_config.draft_actions.show_license_edit %} -
  • - {% url "content:edit" content.pk content.slug as edit_url %} - {% trans "Éditer" %} -
  • - {% endif %} - {% if display_config.draft_actions.show_import_link %}
  • {% url "content:import" content.pk content.slug as import_url %} diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py index 63ed611bb9..a9d73013e2 100644 --- a/zds/tutorialv2/forms.py +++ b/zds/tutorialv2/forms.py @@ -14,7 +14,7 @@ from zds.tutorialv2.models.database import PublishableContent from django.utils.translation import gettext_lazy as _ from zds.utils.forms import IncludeEasyMDE -from zds.utils.validators import with_svg_validator, slugify_raise_on_invalid, InvalidSlugError +from zds.utils.validators import slugify_raise_on_invalid, InvalidSlugError class FormWithTitle(forms.Form): @@ -108,32 +108,14 @@ def __init__(self, *args, **kwargs): class ContentForm(ContainerForm): - type = forms.ChoiceField(choices=TYPE_CHOICES, required=False) + type = forms.ChoiceField(choices=TYPE_CHOICES, required=True) def _create_layout(self): self.helper.layout = Layout( IncludeEasyMDE(), Field("title"), Field("type"), - Field("introduction", css_class="md-editor preview-source"), - ButtonHolder( - StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn"), - ), - HTML( - '{% if form.introduction.value %}{% include "misc/preview.part.html" \ - with text=form.introduction.value %}{% endif %}' - ), - Field("conclusion", css_class="md-editor preview-source"), - ButtonHolder( - StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn"), - ), - HTML( - '{% if form.conclusion.value %}{% include "misc/preview.part.html" \ - with text=form.conclusion.value %}{% endif %}' - ), - Field("last_hash"), - Field("msg_commit"), - ButtonHolder(StrictButton("Valider", type="submit")), + StrictButton("Valider", type="submit"), ) def __init__(self, *args, **kwargs): @@ -148,32 +130,6 @@ def __init__(self, *args, **kwargs): self.helper["type"].wrap(Field, disabled=True) -class EditContentForm(ContentForm): - title = None - description = None - type = None - - def _create_layout(self): - self.helper.layout = Layout( - IncludeEasyMDE(), - Field("introduction", css_class="md-editor preview-source"), - StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn"), - HTML( - '{% if form.introduction.value %}{% include "misc/preview.part.html" \ - with text=form.introduction.value %}{% endif %}' - ), - Field("conclusion", css_class="md-editor preview-source"), - StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn"), - HTML( - '{% if form.conclusion.value %}{% include "misc/preview.part.html" \ - with text=form.conclusion.value %}{% endif %}' - ), - Field("last_hash"), - Field("msg_commit"), - ButtonHolder(StrictButton("Valider", type="submit")), - ) - - class ExtractForm(FormWithTitle): text = forms.CharField( label=_("Texte"), diff --git a/zds/tutorialv2/mixins.py b/zds/tutorialv2/mixins.py index 2d1b80a6bb..520309efdb 100644 --- a/zds/tutorialv2/mixins.py +++ b/zds/tutorialv2/mixins.py @@ -182,7 +182,7 @@ def form_invalid(self, form): class FormWithPreview(FormView): def post(self, request, *args, **kwargs): - form = self.form_class(request.POST) + form = self.get_form() if "preview" in request.POST: self.form_invalid(form) diff --git a/zds/tutorialv2/tests/models/tests_models.py b/zds/tutorialv2/tests/models/tests_models.py index 465a670516..e73cb70fd5 100644 --- a/zds/tutorialv2/tests/models/tests_models.py +++ b/zds/tutorialv2/tests/models/tests_models.py @@ -449,26 +449,34 @@ def test_publication_and_attributes_consistency(self): old_description = article.public_version.description() article.licence = LicenceFactory() article.save() + self.client.force_login(self.user_author) - self.client.post( - reverse("content:edit", args=[article.pk, article.slug]), - { - "title": old_title + "bla", - "description": old_description + "bla", - "type": "ARTICLE", - "licence": article.licence.pk, - "subcategory": SubCategoryFactory().pk, - "last_hash": article.sha_draft, - }, + + new_title = old_title + "bla" + result = self.client.post( + reverse("content:edit-title", args=[article.pk]), + {"title": new_title}, + follow=True, ) + self.assertEqual(result.status_code, 200) + + new_description = old_description + "bla" + result = self.client.post( + reverse("content:edit-subtitle", args=[article.pk]), + {"subtitle": new_description}, + follow=True, + ) + self.assertEqual(result.status_code, 200) + article = PublishableContent.objects.prefetch_related("public_version").get(pk=article.pk) article.public_version.load_public_version() self.assertEqual(old_title, article.public_version.title()) self.assertEqual(old_description, article.public_version.description()) self.assertEqual(old_date, article.public_version.publication_date) + publish_content(article, article.load_version(), False) - article = PublishableContent.objects.get(pk=article.pk) - article.public_version.load_public_version() + + article = PublishableContent.objects.prefetch_related("public_version").get(pk=article.pk) self.assertEqual(old_date, article.public_version.publication_date) self.assertNotEqual(old_date, article.public_version.update_date) diff --git a/zds/tutorialv2/tests/tests_front.py b/zds/tutorialv2/tests/tests_front.py index 02e0086629..5d8eb5931f 100644 --- a/zds/tutorialv2/tests/tests_front.py +++ b/zds/tutorialv2/tests/tests_front.py @@ -121,7 +121,7 @@ def test_collaborative_article_edition_and_editor_persistence(self): article.sha_draft = versioned_article.repo_update("article", "", "", update_slug=False) article.save() - article_edit_url = reverse("content:edit", args=[article.pk, article.slug]) + article_edit_url = reverse("content:edit-introduction", args=[article.pk]) self.login(author) selenium.execute_script('localStorage.setItem("editor_choice", "new")') # we want the new editor @@ -130,7 +130,7 @@ def test_collaborative_article_edition_and_editor_persistence(self): intro = self.find_element("div#div_id_introduction div.CodeMirror") # ActionChains: Support for CodeMirror https://stackoverflow.com/a/48969245/2226755 action_chains = ActionChains(selenium) - scrollDriverTo(selenium, 0, 312) + scroll_driver_to(selenium, 0, 312) action_chains.click(intro).perform() action_chains.send_keys("intro").perform() @@ -144,36 +144,7 @@ def test_collaborative_article_edition_and_editor_persistence(self): self.assertEqual("new intro", self.find_element(".md-editor#id_introduction").get_attribute("value")) - def test_the_editor_forgets_its_content_on_form_submission(self): - selenium = self.selenium - - author = ProfileFactory() - - self.login(author) - selenium.execute_script('localStorage.setItem("editor_choice", "new")') # we want the new editor - new_article_url = self.live_server_url + reverse( - "content:create-content", kwargs={"created_content_type": "ARTICLE"} - ) - selenium.get(new_article_url) - WebDriverWait(self.selenium, 10).until(ec.element_to_be_clickable((By.CSS_SELECTOR, "#id_title"))).click() - - self.find_element("#id_title").send_keys("Oulipo") - - intro = self.find_element("div#div_id_introduction div.CodeMirror") - action_chains = ActionChains(selenium) - scrollDriverTo(selenium, 0, 312) - action_chains.click(intro).perform() - action_chains.send_keys("Le cadavre exquis boira le vin nouveau.").perform() - - self.find_element(".content-container button[type=submit]").click() - - self.assertTrue(WebDriverWait(selenium, 10).until(ec.title_contains("Oulipo"))) - - selenium.get(new_article_url) - - self.assertEqual("", self.find_element(".md-editor#id_introduction").get_attribute("value")) - -def scrollDriverTo(driver, x, y): - scriptScrollTo = f"window.scrollTo({x}, {y});" - driver.execute_script(scriptScrollTo) +def scroll_driver_to(driver, x, y): + script_scroll_to = f"window.scrollTo({x}, {y});" + driver.execute_script(script_scroll_to) diff --git a/zds/tutorialv2/tests/tests_lists.py b/zds/tutorialv2/tests/tests_lists.py index fbaa04603e..0012802b05 100644 --- a/zds/tutorialv2/tests/tests_lists.py +++ b/zds/tutorialv2/tests/tests_lists.py @@ -118,7 +118,7 @@ def test_list_categories(self): context_categories = list(resp.context_data["categories"]) self.assertEqual(context_categories[0].contents_count, 10) - self.assertEqual(context_categories[0].subcategories, [subcategory_1, subcategory_2]) + self.assertCountEqual(context_categories[0].subcategories, [subcategory_1, subcategory_2]) self.assertIn(category_1, context_categories) def test_private_lists(self): diff --git a/zds/tutorialv2/tests/tests_views/tests_content.py b/zds/tutorialv2/tests/tests_views/tests_content.py index 14fe2429d9..2c629e1109 100644 --- a/zds/tutorialv2/tests/tests_views/tests_content.py +++ b/zds/tutorialv2/tests/tests_views/tests_content.py @@ -230,89 +230,39 @@ def test_ensure_access(self): ) self.assertEqual(result.status_code, 200) - def test_basic_tutorial_workflow(self): - """General test on the basic workflow of a tutorial: creation, edition, deletion for the author""" + def test_create_tutorial(self): + """Test the creation of a new content.""" self.client.force_login(self.user_author) - # create tutorial - intro = "une intro" - conclusion = "une conclusion" - description = "une description" title = "un titre" - random = "un truc à la rien à voir" - random_with_md = "un text contenant du **markdown** ." - - response = self.client.post( - reverse("content:create-content", kwargs={"created_content_type": "TUTORIAL"}), - { - "text": random_with_md, - "preview": "", - }, - HTTP_X_REQUESTED_WITH="XMLHttpRequest", - ) - - self.assertEqual(200, response.status_code) - - result_string = "".join(str(a, "utf-8") for a in response.streaming_content) - self.assertIn("markdown", result_string, "We need the text to be properly formatted") - result = self.client.post( reverse("content:create-content", kwargs={"created_content_type": "TUTORIAL"}), - { - "title": title, - "introduction": intro, - "conclusion": conclusion, - "type": "TUTORIAL", - "licence": self.licence.pk, - }, + {"title": title, "type": "TUTORIAL"}, follow=False, ) self.assertEqual(result.status_code, 302) self.assertEqual(PublishableContent.objects.all().count(), 2) tuto = PublishableContent.objects.last() - pk = tuto.pk - slug = tuto.slug - versioned = tuto.load_version() - self.assertEqual(Gallery.objects.filter(pk=tuto.gallery.pk).count(), 1) self.assertEqual(UserGallery.objects.filter(gallery__pk=tuto.gallery.pk).count(), tuto.authors.count()) - # access to tutorial - result = self.client.get(reverse("content:edit", args=[pk, slug]), follow=False) - self.assertEqual(result.status_code, 200) - - # preview tutorial - result = self.client.post( - reverse("content:edit", args=[pk, slug]), - {"text": random_with_md, "last_hash": versioned.compute_hash(), "preview": ""}, - HTTP_X_REQUESTED_WITH="XMLHttpRequest", - ) - - self.assertEqual(result.status_code, 200) - - result_string = "".join(a.decode() for a in result.streaming_content) - self.assertIn("markdown", result_string, "We need the text to be properly formatted") + def test_basic_tutorial_workflow(self): + """General test on the basic workflow of a tutorial: edition, deletion for the author""" + self.client.force_login(self.user_author) - result = self.client.post( - reverse("content:edit", args=[pk, slug]), - { - "introduction": random, - "conclusion": random, - "type": "TUTORIAL", - "subcategory": self.subcategory.pk, - "last_hash": versioned.compute_hash(), - }, - follow=False, - ) - self.assertEqual(result.status_code, 302) + # create tutorial + intro = "une intro" + conclusion = "une conclusion" + description = "une description" + title = "un titre" + random = "un truc à la rien à voir" + random_with_md = "un text contenant du **markdown** ." - tuto = PublishableContent.objects.get(pk=pk) - self.assertEqual(tuto.licence, None) - versioned = tuto.load_version() - self.assertEqual(versioned.get_introduction(), random) - self.assertEqual(versioned.get_conclusion(), random) - self.assertEqual(versioned.licence, None) + tuto = PublishableContentFactory(type="TUTORIAL") + tuto.authors.add(self.user_author) + pk = tuto.pk + slug = tuto.slug # preview container result = self.client.post( @@ -930,18 +880,10 @@ def test_export_content(self): given_title = "Oh, le beau titre à lire !" some_text = "À lire à un moment ou un autre, Über utile" # accentuated characters are important for the test - # create a tutorial + # Create a tutorial and modify its introduction and conclusion result = self.client.post( reverse("content:create-content", kwargs={"created_content_type": "TUTORIAL"}), - { - "title": given_title, - "description": some_text, - "introduction": some_text, - "conclusion": some_text, - "type": "TUTORIAL", - "licence": self.licence.pk, - "subcategory": self.subcategory.pk, - }, + {"title": given_title, "type": "TUTORIAL"}, follow=False, ) self.assertEqual(result.status_code, 302) @@ -951,6 +893,20 @@ def test_export_content(self): tuto_pk = tuto.pk tuto_slug = tuto.slug + result = self.client.post( + reverse("content:edit-introduction", args=[tuto.pk]), + {"introduction": some_text}, + follow=False, + ) + self.assertEqual(result.status_code, 302) + + result = self.client.post( + reverse("content:edit-conclusion", args=[tuto.pk]), + {"conclusion": some_text}, + follow=False, + ) + self.assertEqual(result.status_code, 302) + # add a chapter result = self.client.post( reverse("content:create-container", args=[tuto_pk, tuto_slug]), @@ -1093,14 +1049,7 @@ def test_import_create_content(self): # create a tutorial result = self.client.post( reverse("content:create-content", kwargs={"created_content_type": "TUTORIAL"}), - { - "title": given_title, - "description": some_text, - "introduction": some_text, - "conclusion": some_text, - "type": "TUTORIAL", - "subcategory": self.subcategory.pk, - }, + {"title": given_title, "type": "TUTORIAL"}, follow=False, ) self.assertEqual(result.status_code, 302) @@ -1110,6 +1059,20 @@ def test_import_create_content(self): tuto_pk = tuto.pk tuto_slug = tuto.slug + result = self.client.post( + reverse("content:edit-introduction", args=[tuto.pk]), + {"introduction": some_text}, + follow=False, + ) + self.assertEqual(result.status_code, 302) + + result = self.client.post( + reverse("content:edit-conclusion", args=[tuto.pk]), + {"conclusion": some_text}, + follow=False, + ) + self.assertEqual(result.status_code, 302) + # add a chapter result = self.client.post( reverse("content:create-container", args=[tuto_pk, tuto_slug]), @@ -1209,15 +1172,7 @@ def test_import_in_existing_content(self): # create a tutorial result = self.client.post( reverse("content:create-content", kwargs={"created_content_type": "TUTORIAL"}), - { - "title": given_title, - "description": some_text, - "introduction": some_text, - "conclusion": some_text, - "type": "TUTORIAL", - "licence": self.licence.pk, - "subcategory": self.subcategory.pk, - }, + {"title": given_title, "type": "TUTORIAL"}, follow=False, ) self.assertEqual(result.status_code, 302) @@ -1227,6 +1182,20 @@ def test_import_in_existing_content(self): tuto_pk = tuto.pk tuto_slug = tuto.slug + result = self.client.post( + reverse("content:edit-introduction", args=[tuto.pk]), + {"introduction": some_text}, + follow=False, + ) + self.assertEqual(result.status_code, 302) + + result = self.client.post( + reverse("content:edit-conclusion", args=[tuto.pk]), + {"conclusion": some_text}, + follow=False, + ) + self.assertEqual(result.status_code, 302) + # add a chapter result = self.client.post( reverse("content:create-container", args=[tuto_pk, tuto_slug]), @@ -1652,21 +1621,12 @@ def test_validation_subscription(self): # Re-ask a new validation self.client.force_login(self.user_author) + # Update the title to spice things up tuto = PublishableContent.objects.get(pk=tuto.pk) versioned = tuto.load_version() self.client.post( - reverse("content:edit", args=[tuto.pk, tuto.slug]), - { - "title": "new title so that everything explode", - "description": tuto.description, - "introduction": tuto.load_version().get_introduction(), - "conclusion": tuto.load_version().get_conclusion(), - "type": "ARTICLE", - "licence": tuto.licence.pk, - "subcategory": self.subcategory.pk, - "last_hash": tuto.load_version(tuto.sha_draft).compute_hash(), - "image": (settings.BASE_DIR / "fixtures" / "logo.png").open("rb"), - }, + reverse("content:edit-title", args=[tuto.pk]), + {"title": "new title so that everything explode"}, follow=False, ) @@ -2532,55 +2492,6 @@ def test_concurent_edition(self): self.client.force_login(self.user_author) - # no hash, no edition - result = self.client.post( - reverse("content:edit", args=[tuto.pk, tuto.slug]), - { - "title": tuto.title, - "description": tuto.description, - "introduction": random, - "conclusion": random, - "type": "TUTORIAL", - "licence": self.licence.pk, - "subcategory": self.subcategory.pk, - "last_hash": "", - }, - follow=True, - ) - self.assertEqual(result.status_code, 200) - - msgs = result.context["messages"] - last = None - for msg in msgs: - last = msg - self.assertEqual(last.level, messages.ERROR) - - tuto = PublishableContent.objects.get(pk=tuto.pk) - versioned = tuto.load_version() - self.assertNotEqual(versioned.get_introduction(), random) - self.assertNotEqual(versioned.get_conclusion(), random) - - result = self.client.post( - reverse("content:edit", args=[tuto.pk, tuto.slug]), - { - "title": tuto.title, - "description": tuto.description, - "introduction": random, - "conclusion": random, - "type": "TUTORIAL", - "licence": self.licence.pk, - "subcategory": self.subcategory.pk, - "last_hash": versioned.compute_hash(), # good hash - }, - follow=True, - ) - self.assertEqual(result.status_code, 200) - - tuto = PublishableContent.objects.get(pk=tuto.pk) - versioned = tuto.load_version() - self.assertEqual(versioned.get_introduction(), random) - self.assertEqual(versioned.get_conclusion(), random) - # edit container: result = self.client.post( reverse( diff --git a/zds/tutorialv2/tests/tests_views/tests_display.py b/zds/tutorialv2/tests/tests_views/tests_display.py index feeaeb3c1c..a58800ab0f 100644 --- a/zds/tutorialv2/tests/tests_views/tests_display.py +++ b/zds/tutorialv2/tests/tests_views/tests_display.py @@ -4,7 +4,6 @@ from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse -from django.utils.translation import gettext_lazy as _ from zds.member.tests.factories import ProfileFactory from zds.tutorialv2.tests import TutorialTestMixin @@ -34,23 +33,14 @@ def setUp(self): self.user_author = ProfileFactory().user self.client.force_login(self.user_author) - # Publish an article: + # Publish an article self.article = PublishedContentFactory(author_list=[self.user_author], type="ARTICLE") def _new_draft_version(self, text): - # Create a new draft version: - versioned = self.article.load_version() + """Create a new draft version.""" result = self.client.post( - reverse("content:edit", args=[self.article.pk, self.article.slug]), - { - "title": self.article.title, - "description": self.article.description, - "introduction": text, - "conclusion": "Modified conclusion", - "type": self.article.type, - "subcategory": self.article.subcategory.first().pk, - "last_hash": versioned.compute_hash(), - }, + reverse("content:edit-introduction", args=[self.article.pk]), + {"introduction": text}, follow=False, ) self.assertEqual(result.status_code, 302) @@ -84,7 +74,7 @@ def common_tests(): self.assertNotContains(public_page, PublicActionsState.messages["draft_is_same"]) self.assertContains(public_page, PublicActionsState.messages["draft_is_more_recent"]) - # Now a new draft version, to have different version from validation: + # Now a new draft version, to have different version from validation self._new_draft_version(self.TEXT_SECOND_MODIFICATION) public_page = common_tests() @@ -123,7 +113,7 @@ def common_tests(): self.assertNotContains(draft_page, PublicActionsState.messages["public_is_same"]) self.assertContains(draft_page, ValidationActions.messages["validation_is_same"]) - # Now a new draft version, to have different version from validation: + # Now a new draft version, to have different version from validation self._new_draft_version(self.TEXT_SECOND_MODIFICATION) draft_page = common_tests() @@ -150,9 +140,7 @@ def common_tests(): self._new_draft_version(self.TEXT_FIRST_MODIFICATION) request_validation(self.article) - validation_page = common_tests() - - # Now a new draft version, to have different version from validation: + # Now a new draft version, to have different version from validation self._new_draft_version(self.TEXT_SECOND_MODIFICATION) validation_page = common_tests() diff --git a/zds/tutorialv2/tests/tests_views/tests_editconclusionview.py b/zds/tutorialv2/tests/tests_views/tests_editconclusionview.py new file mode 100644 index 0000000000..fcd99142d0 --- /dev/null +++ b/zds/tutorialv2/tests/tests_views/tests_editconclusionview.py @@ -0,0 +1,96 @@ +from datetime import datetime + +from django.test import TestCase +from django.urls import reverse + +from zds.tutorialv2.tests import TutorialTestMixin, override_for_contents +from zds.tutorialv2.tests.factories import PublishableContentFactory +from zds.member.tests.factories import ProfileFactory, StaffProfileFactory + + +view_name = "content:edit-conclusion" + + +@override_for_contents() +class PermissionTests(TutorialTestMixin, TestCase): + """Test permissions and associated behaviors, such as redirections and status codes.""" + + def setUp(self): + # Create users + self.author = ProfileFactory().user + self.staff = StaffProfileFactory().user + self.outsider = ProfileFactory().user + + # Create a content + self.content = PublishableContentFactory(author_list=[self.author]) + + # Get information to be reused in tests + self.form_url = reverse(view_name, kwargs={"pk": self.content.pk}) + self.form_data = {"conclusion": "conclusion content"} # avoids changing the slug + self.content_data = {"pk": self.content.pk, "slug": self.content.slug} + self.content_url = reverse("content:view", kwargs=self.content_data) + self.login_url = reverse("member-login") + "?next=" + self.form_url + + def test_not_authenticated(self): + """Test that on form submission, unauthenticated users are redirected to the login page.""" + self.client.logout() # ensure no user is authenticated + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.login_url) + + def test_authenticated_author(self): + """Test that on form submission, authors are redirected to the content page.""" + self.client.force_login(self.author) + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.content_url) + + def test_authenticated_staff(self): + """Test that on form submission, staffs are redirected to the content page.""" + self.client.force_login(self.staff) + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.content_url) + + def test_authenticated_outsider(self): + """Test that on form submission, unauthorized users get a 403.""" + self.client.force_login(self.outsider) + response = self.client.post(self.form_url, self.form_data) + self.assertEqual(response.status_code, 403) + + +@override_for_contents() +class FunctionalTests(TutorialTestMixin, TestCase): + """Test the detailed behavior of the feature, such as updates of the database or repositories.""" + + def setUp(self): + self.author = ProfileFactory() + self.content = PublishableContentFactory(author_list=[self.author.user]) + self.form_url = reverse(view_name, kwargs={"pk": self.content.pk}) + self.client.force_login(self.author.user) + + def test_normal(self): + start_date = datetime.now() + self.assertTrue(self.content.update_date < start_date) + + new_conclusion = "Ceci n'est pas l'ancienne conclusion" + response = self.client.post(self.form_url, {"conclusion": new_conclusion}, follow=True) + self.assertEqual(response.status_code, 200) + + self.content.refresh_from_db() + + # Database update + self.assertTrue(self.content.update_date > start_date) + + # Update in repository + versioned_content = self.content.load_version() + self.assertEqual(versioned_content.get_conclusion(), new_conclusion) + + def test_preview(self): + some_markdown = "Ceci est un texte avec du **markdown**" + expected_preview = "Ceci est un texte avec du markdown" + + response = self.client.post( + self.form_url, {"text": some_markdown, "preview": ""}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) + + self.assertEqual(200, response.status_code) + result_string = "".join(str(a, "utf-8") for a in response.streaming_content) + self.assertIn(expected_preview, result_string, "We need the text to be properly formatted") diff --git a/zds/tutorialv2/tests/tests_views/tests_editintroductionview.py b/zds/tutorialv2/tests/tests_views/tests_editintroductionview.py new file mode 100644 index 0000000000..94b9bd1159 --- /dev/null +++ b/zds/tutorialv2/tests/tests_views/tests_editintroductionview.py @@ -0,0 +1,96 @@ +from datetime import datetime + +from django.test import TestCase +from django.urls import reverse + +from zds.tutorialv2.tests import TutorialTestMixin, override_for_contents +from zds.tutorialv2.tests.factories import PublishableContentFactory +from zds.member.tests.factories import ProfileFactory, StaffProfileFactory + + +view_name = "content:edit-introduction" + + +@override_for_contents() +class PermissionTests(TutorialTestMixin, TestCase): + """Test permissions and associated behaviors, such as redirections and status codes.""" + + def setUp(self): + # Create users + self.author = ProfileFactory().user + self.staff = StaffProfileFactory().user + self.outsider = ProfileFactory().user + + # Create a content + self.content = PublishableContentFactory(author_list=[self.author]) + + # Get information to be reused in tests + self.form_url = reverse(view_name, kwargs={"pk": self.content.pk}) + self.form_data = {"introduction": "introduction content"} # avoids changing the slug + self.content_data = {"pk": self.content.pk, "slug": self.content.slug} + self.content_url = reverse("content:view", kwargs=self.content_data) + self.login_url = reverse("member-login") + "?next=" + self.form_url + + def test_not_authenticated(self): + """Test that on form submission, unauthenticated users are redirected to the login page.""" + self.client.logout() # ensure no user is authenticated + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.login_url) + + def test_authenticated_author(self): + """Test that on form submission, authors are redirected to the content page.""" + self.client.force_login(self.author) + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.content_url) + + def test_authenticated_staff(self): + """Test that on form submission, staffs are redirected to the content page.""" + self.client.force_login(self.staff) + response = self.client.post(self.form_url, self.form_data) + self.assertRedirects(response, self.content_url) + + def test_authenticated_outsider(self): + """Test that on form submission, unauthorized users get a 403.""" + self.client.force_login(self.outsider) + response = self.client.post(self.form_url, self.form_data) + self.assertEqual(response.status_code, 403) + + +@override_for_contents() +class FunctionalTests(TutorialTestMixin, TestCase): + """Test the detailed behavior of the feature, such as updates of the database or repositories.""" + + def setUp(self): + self.author = ProfileFactory() + self.content = PublishableContentFactory(author_list=[self.author.user]) + self.form_url = reverse(view_name, kwargs={"pk": self.content.pk}) + self.client.force_login(self.author.user) + + def test_normal(self): + start_date = datetime.now() + self.assertTrue(self.content.update_date < start_date) + + new_introduction = "Ceci n'est pas l'ancienne introduction" + response = self.client.post(self.form_url, {"introduction": new_introduction}, follow=True) + self.assertEqual(response.status_code, 200) + + self.content.refresh_from_db() + + # Database update + self.assertTrue(self.content.update_date > start_date) + + # Update in repository + versioned_content = self.content.load_version() + self.assertEqual(versioned_content.get_introduction(), new_introduction) + + def test_preview(self): + some_markdown = "Ceci est un texte avec du **markdown**" + expected_preview = "Ceci est un texte avec du markdown" + + response = self.client.post( + self.form_url, {"text": some_markdown, "preview": ""}, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + ) + + self.assertEqual(200, response.status_code) + result_string = "".join(str(a, "utf-8") for a in response.streaming_content) + self.assertIn(expected_preview, result_string, "We need the text to be properly formatted") diff --git a/zds/tutorialv2/tests/tests_views/tests_published.py b/zds/tutorialv2/tests/tests_views/tests_published.py index ee8cb71250..a09c5af73f 100644 --- a/zds/tutorialv2/tests/tests_views/tests_published.py +++ b/zds/tutorialv2/tests/tests_views/tests_published.py @@ -1287,17 +1287,8 @@ def test_validation_list_has_good_title(self): old_title = tuto.title new_title = "a brand new title" self.client.post( - reverse("content:edit", args=[tuto.pk, tuto.slug]), - { - "title": new_title, - "description": tuto.description, - "introduction": "a", - "conclusion": "b", - "type": "TUTORIAL", - "licence": self.licence.pk, - "subcategory": self.subcategory.pk, - "last_hash": tuto.sha_draft, - }, + reverse("content:edit-title", args=[tuto.pk]), + {"title": new_title}, follow=False, ) self.client.logout() @@ -1332,18 +1323,8 @@ def test_unpublish_with_title_change(self): registered_validation.save() self.client.force_login(self.user_staff) self.client.post( - reverse("content:edit", args=[article.pk, article.slug]), - { - "title": "new title so that everything explode", - "description": article.description, - "introduction": article.load_version().get_introduction(), - "conclusion": article.load_version().get_conclusion(), - "type": "ARTICLE", - "licence": article.licence.pk, - "subcategory": self.subcategory.pk, - "last_hash": article.load_version(article.sha_draft).compute_hash(), - "image": (settings.BASE_DIR / "fixtures" / "logo.png").open("rb"), - }, + reverse("content:edit-title", args=[article.pk]), + {"title": "new title so that everything explode"}, follow=False, ) public_count = PublishedContent.objects.count() @@ -1418,17 +1399,8 @@ def test_validation_history(self): published = PublishedContentFactory(author_list=[self.user_author]) self.client.force_login(self.user_author) result = self.client.post( - reverse("content:edit", args=[published.pk, published.slug]), - { - "title": published.title, - "description": published.description, - "introduction": "crappy crap", - "conclusion": "crappy crap", - "type": "TUTORIAL", - "licence": self.licence.pk, - "subcategory": self.subcategory.pk, - "last_hash": published.load_version().compute_hash(), # good hash - }, + reverse("content:edit-subtitle", args=[published.pk]), + {"subtitle": "Sous-titre qui fait une nouvelle version"}, follow=True, ) self.assertEqual(result.status_code, 200) @@ -1492,18 +1464,8 @@ def test_ask_validation_update(self): # login with user, edit content and ask validation for update self.client.force_login(self.user_author) result = self.client.post( - reverse("content:edit", args=[content_draft.pk, content_draft.slug]), - { - "title": content_draft.title + "2", - "description": content_draft.description, - "introduction": content_draft.introduction, - "conclusion": content_draft.conclusion, - "type": content_draft.type, - "licence": self.licence.pk, - "subcategory": self.subcategory.pk, - "last_hash": content_draft.compute_hash(), - "image": content_draft.image or "None", - }, + reverse("content:edit-title", args=[content_draft.pk]), + {"title": content_draft.title + "2"}, follow=False, ) self.assertEqual(result.status_code, 302) diff --git a/zds/tutorialv2/urls/urls_contents.py b/zds/tutorialv2/urls/urls_contents.py index 32cb55f869..89da39e1e1 100644 --- a/zds/tutorialv2/urls/urls_contents.py +++ b/zds/tutorialv2/urls/urls_contents.py @@ -4,11 +4,12 @@ from zds.tutorialv2.views.canonical import EditCanonicalLinkView from zds.tutorialv2.views.categories import EditCategoriesView from zds.tutorialv2.views.contents import ( - CreateContent, - EditContent, + CreateContentView, DeleteContent, EditTitle, EditSubtitle, + EditIntroductionView, + EditConclusionView, ) from zds.tutorialv2.views.thumbnail import EditThumbnailView from zds.tutorialv2.views.display.container import ContainerValidationView @@ -168,7 +169,7 @@ def get_version_pages(): # typo: path("reactions/typo/", WarnTypo.as_view(), name="warn-typo"), # create: - path("nouveau-contenu//", CreateContent.as_view(), name="create-content"), + path("nouveau-contenu//", CreateContentView.as_view(), name="create-content"), path( "nouveau-conteneur////", CreateContainer.as_view(), @@ -208,7 +209,6 @@ def get_version_pages(): name="edit-extract", ), path("editer-section////", EditExtract.as_view(), name="edit-extract"), - path("editer///", EditContent.as_view(), name="edit"), path("deplacer/", MoveChild.as_view(), name="move-element"), path("historique///", DisplayHistory.as_view(), name="history"), path("comparaison///", DisplayDiff.as_view(), name="diff"), @@ -219,6 +219,8 @@ def get_version_pages(): path("modifier-titre//", EditTitle.as_view(), name="edit-title"), path("modifier-sous-titre//", EditSubtitle.as_view(), name="edit-subtitle"), path("modifier-miniature//", EditThumbnailView.as_view(), name="edit-thumbnail"), + path("modifier-introduction//", EditIntroductionView.as_view(), name="edit-introduction"), + path("modifier-conclusion//", EditConclusionView.as_view(), name="edit-conclusion"), path("modifier-licence//", EditContentLicense.as_view(), name="edit-license"), path("modifier-tags//", EditTags.as_view(), name="edit-tags"), path("modifier-lien-canonique/", EditCanonicalLinkView.as_view(), name="edit-canonical-link"), diff --git a/zds/tutorialv2/utils.py b/zds/tutorialv2/utils.py index eaa0739934..a7cfce605d 100644 --- a/zds/tutorialv2/utils.py +++ b/zds/tutorialv2/utils.py @@ -507,7 +507,7 @@ def fill_containers_from_json(json_sub, parent): raise BadManifestError(_("Type d'objet inconnu : « {} »").format(child["object"])) -def init_new_repo(db_object, introduction_text, conclusion_text, commit_message="", do_commit=True): +def init_new_repo(db_object, introduction_text="", conclusion_text="", commit_message="", do_commit=True): """Create a new repository in ``settings.ZDS_APP['contents']['private_repo']``\ to store the files for a new content. Note that ``db_object.sha_draft`` will be set to the good value diff --git a/zds/tutorialv2/views/contents.py b/zds/tutorialv2/views/contents.py index 21c9380110..036258b610 100644 --- a/zds/tutorialv2/views/contents.py +++ b/zds/tutorialv2/views/contents.py @@ -3,27 +3,23 @@ from crispy_forms.bootstrap import StrictButton from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Field +from crispy_forms.layout import Layout, Field, HTML, ButtonHolder from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction -from django.forms import forms, CharField +from django.forms import forms, CharField, Textarea, TextInput from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ -from django.views.generic import DeleteView +from django.views.generic import DeleteView, FormView from zds.gallery.models import Gallery from zds.member.decorator import LoggedWithReadWriteHability from zds.member.utils import get_bot_account -from zds.tutorialv2.forms import ( - ContentForm, - FormWithTitle, - EditContentForm, -) +from zds.tutorialv2.forms import ContentForm, FormWithTitle from zds.tutorialv2.mixins import ( SingleContentFormViewMixin, SingleContentViewMixin, @@ -32,6 +28,7 @@ from zds.tutorialv2.models.database import PublishableContent, Validation from zds.tutorialv2.utils import init_new_repo from zds.tutorialv2.views.authors import RemoveAuthorFromContent +from zds.utils.forms import IncludeEasyMDE from zds.utils.models import get_hat_from_settings from zds.mp.utils import send_mp, send_message_mp from zds.utils.uuslug_wrapper import slugify @@ -40,25 +37,15 @@ logger = logging.getLogger(__name__) -class CreateContent(LoggedWithReadWriteHability, FormWithPreview): - """ - Handle content creation. Since v22 a licence must be explicitly selected - instead of defaulting to "All rights reserved". Users can however - set a default licence in their profile. - """ - +class CreateContentView(LoggedWithReadWriteHability, FormView): template_name = "tutorialv2/create/content.html" model = PublishableContent form_class = ContentForm - content = None - created_content_type = "TUTORIAL" def get_form(self, form_class=ContentForm): form = super().get_form(form_class) content_type = self.kwargs["created_content_type"] - if content_type in CONTENT_TYPE_LIST: - self.created_content_type = content_type - form.initial["type"] = self.created_content_type + form.initial["type"] = content_type if content_type in CONTENT_TYPE_LIST else "TUTORIAL" return form def get_context_data(self, **kwargs): @@ -88,12 +75,7 @@ def form_valid(self, form): self.content.ensure_author_gallery() # Create a new git repository - init_new_repo( - self.content, - form.cleaned_data["introduction"], - form.cleaned_data["conclusion"], - form.cleaned_data["msg_commit"], - ) + init_new_repo(self.content) return super().form_valid(form) @@ -101,62 +83,6 @@ def get_success_url(self): return reverse("content:view", args=[self.content.pk, self.content.slug]) -class EditContent(LoggedWithReadWriteHability, SingleContentFormViewMixin, FormWithPreview): - template_name = "tutorialv2/edit/content.html" - model = PublishableContent - form_class = EditContentForm - - def get_initial(self): - """rewrite function to pre-populate form""" - initial = super().get_initial() - versioned = self.versioned_object - - initial["introduction"] = versioned.get_introduction() - initial["conclusion"] = versioned.get_conclusion() - initial["subcategory"] = self.object.subcategory.all() - initial["last_hash"] = versioned.compute_hash() - - return initial - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - if "preview" not in self.request.POST: - context["gallery"] = self.object.gallery - - return context - - def form_valid(self, form): - versioned = self.versioned_object - publishable = self.object - - # check if content has changed: - current_hash = versioned.compute_hash() - if current_hash != form.cleaned_data["last_hash"]: - data = form.data.copy() - data["last_hash"] = current_hash - data["introduction"] = versioned.get_introduction() - data["conclusion"] = versioned.get_conclusion() - form.data = data - messages.error(self.request, _("Une nouvelle version a été postée avant que vous ne validiez.")) - return self.form_invalid(form) - - publishable.update_date = datetime.now() - - # now, update the versioned information - sha = versioned.repo_update_top_container( - publishable.title, - publishable.slug, - form.cleaned_data["introduction"], - form.cleaned_data["conclusion"], - form.cleaned_data["msg_commit"], - ) - publishable.sha_draft = sha - publishable.save() - - self.success_url = reverse("content:view", args=[publishable.pk, publishable.slug]) - return super().form_valid(form) - - class EditTitleForm(FormWithTitle): def __init__(self, versioned_content, *args, **kwargs): kwargs["initial"] = {"title": versioned_content.title} @@ -313,6 +239,198 @@ def update_sha_draft(publishable_content, sha): publishable_content.save() +class EditIntroductionForm(forms.Form): + introduction = CharField( + label=_("Introduction"), + required=False, + widget=Textarea( + attrs={"placeholder": _("Votre introduction, au format Markdown."), "class": "md-editor preview-source"} + ), + ) + + commit_message = CharField( + label=_("Message de suivi"), + max_length=400, + required=False, + widget=TextInput(attrs={"placeholder": _("Un résumé de vos ajouts et modifications.")}), + ) + + def __init__(self, versioned_content, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.helper = FormHelper() + self.helper.form_class = "content-wrapper" + self.helper.form_method = "post" + self.helper.form_action = reverse("content:edit-introduction", kwargs={"pk": versioned_content.pk}) + self.helper.layout = Layout( + IncludeEasyMDE(), + Field("introduction"), + ButtonHolder( + StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn"), + ), + HTML( + """{% if form.introduction.value %}{% include "misc/preview.part.html" with text=form.introduction.value %}{% endif %}""" + ), + Field("commit_message"), + ButtonHolder(StrictButton(_("Modifier"), type="submit")), + ) + + +class EditIntroductionView(LoginRequiredMixin, SingleContentFormViewMixin, FormWithPreview): + model = PublishableContent + form_class = EditIntroductionForm + template_name = "tutorialv2/edit/introduction.html" + success_message = _("L'introduction de la publication a bien été changée.") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if "preview" not in self.request.POST: + context["gallery"] = self.object.gallery + return context + + def get_initial(self): + initial = super().get_initial() + introduction = self.versioned_object.get_introduction() + initial["introduction"] = introduction + return initial + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["versioned_content"] = self.versioned_object + return kwargs + + def form_valid(self, form): + publishable = self.object + versioned = self.versioned_object + + commit_message = "Modification de l'introduction" + if form.cleaned_data["commit_message"] != "": + commit_message = form.cleaned_data["commit_message"] + + sha = self.update_introduction_in_repository( + publishable, versioned, form.cleaned_data["introduction"], commit_message + ) + self.update_sha_draft(publishable, sha) + + messages.success(self.request, self.success_message) + return super().form_valid(form) + + def get_success_url(self): + return reverse("content:view", args=[self.object.pk, self.object.slug]) + + @staticmethod + def update_introduction_in_repository(publishable_content, versioned_content, introduction, commit_message): + sha = versioned_content.repo_update_top_container( + publishable_content.title, + publishable_content.slug, + introduction, + versioned_content.get_conclusion(), + commit_message, + ) + return sha + + @staticmethod + def update_sha_draft(publishable_content, sha): + publishable_content.sha_draft = sha + publishable_content.save() + + +class EditConclusionForm(forms.Form): + conclusion = CharField( + label=_("Conclusion"), + required=False, + widget=Textarea( + attrs={"placeholder": _("Votre conclusion, au format Markdown."), "class": "md-editor preview-source"} + ), + ) + + commit_message = CharField( + label=_("Message de suivi"), + max_length=400, + required=False, + widget=TextInput(attrs={"placeholder": _("Un résumé de vos ajouts et modifications.")}), + ) + + def __init__(self, versioned_content, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.helper = FormHelper() + self.helper.form_class = "content-wrapper" + self.helper.form_method = "post" + self.helper.form_action = reverse("content:edit-conclusion", kwargs={"pk": versioned_content.pk}) + self.helper.layout = Layout( + IncludeEasyMDE(), + Field("conclusion"), + ButtonHolder( + StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn") + ), + HTML( + """{% if form.conclusion.value %}{% include "misc/preview.part.html" with text=form.conclusion.value %}{% endif %}""" + ), + Field("commit_message"), + ButtonHolder(StrictButton(_("Modifier"), type="submit")), + ) + + +class EditConclusionView(LoginRequiredMixin, SingleContentFormViewMixin, FormWithPreview): + model = PublishableContent + form_class = EditConclusionForm + template_name = "tutorialv2/edit/conclusion.html" + success_message = _("La conclusion de la publication a bien été changée.") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + if "preview" not in self.request.POST: + context["gallery"] = self.object.gallery + return context + + def get_initial(self): + initial = super().get_initial() + conclusion = self.versioned_object.get_conclusion() + initial["conclusion"] = conclusion + return initial + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["versioned_content"] = self.versioned_object + return kwargs + + def form_valid(self, form): + publishable = self.object + versioned = self.versioned_object + + commit_message = "Modification de la conclusion" + if form.cleaned_data["commit_message"] != "": + commit_message = form.cleaned_data["commit_message"] + + sha = self.update_conclusion_in_repository( + publishable, versioned, form.cleaned_data["conclusion"], commit_message + ) + self.update_sha_draft(publishable, sha) + + messages.success(self.request, self.success_message) + return super().form_valid(form) + + def get_success_url(self): + return reverse("content:view", args=[self.object.pk, self.object.slug]) + + @staticmethod + def update_conclusion_in_repository(publishable_content, versioned_content, conclusion, commit_message): + sha = versioned_content.repo_update_top_container( + publishable_content.title, + publishable_content.slug, + versioned_content.get_introduction(), + conclusion, + commit_message, + ) + return sha + + @staticmethod + def update_sha_draft(publishable_content, sha): + publishable_content.sha_draft = sha + publishable_content.save() + + class DeleteContent(LoginRequiredMixin, SingleContentViewMixin, DeleteView): model = PublishableContent http_method_names = ["post"]