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 %}
@@ -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 %}
@@ -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"]