+ {% if content.image %}
+
+ {% endif %}
+ {% if show_form %}
+ {% captureas class_modifier %}{% if content.image %}with-thumbnail{% else %}with-placeholder{% endif %}{% endcaptureas %}
+
+ {% trans "Modifier la miniature" %}
+
+ {% endif %}
{% spaceless %}
From 7a45aafbd08f3016888b0affcb4956608bacd355 Mon Sep 17 00:00:00 2001
From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com>
Date: Wed, 8 May 2024 19:55:20 +0200
Subject: [PATCH 2/7] Ajoute le formulaire pour modifier la miniature
---
.../includes/headline/title.part.html | 1 +
zds/tutorialv2/forms.py | 31 ------
zds/tutorialv2/urls/urls_contents.py | 7 +-
zds/tutorialv2/views/contents.py | 65 +++--------
zds/tutorialv2/views/display/content.py | 2 +
zds/tutorialv2/views/thumbnail.py | 102 ++++++++++++++++++
6 files changed, 119 insertions(+), 89 deletions(-)
create mode 100644 zds/tutorialv2/views/thumbnail.py
diff --git a/templates/tutorialv2/includes/headline/title.part.html b/templates/tutorialv2/includes/headline/title.part.html
index 073a502dc6..f2c84c898a 100644
--- a/templates/tutorialv2/includes/headline/title.part.html
+++ b/templates/tutorialv2/includes/headline/title.part.html
@@ -15,6 +15,7 @@
{% trans "Modifier la miniature" %}
+ {% crispy form_edit_thumbnail %}
{% endif %}
{% endif %}
diff --git a/zds/tutorialv2/forms.py b/zds/tutorialv2/forms.py
index 153613fb69..63ed611bb9 100644
--- a/zds/tutorialv2/forms.py
+++ b/zds/tutorialv2/forms.py
@@ -108,29 +108,13 @@ def __init__(self, *args, **kwargs):
class ContentForm(ContainerForm):
- description = forms.CharField(
- label=_("Description"),
- max_length=PublishableContent._meta.get_field("description").max_length,
- required=False,
- )
-
- image = forms.FileField(
- label=_("Sélectionnez le logo du contenu (max. {} Ko).").format(
- str(settings.ZDS_APP["gallery"]["image_max_size"] / 1024)
- ),
- validators=[with_svg_validator],
- required=False,
- )
-
type = forms.ChoiceField(choices=TYPE_CHOICES, required=False)
def _create_layout(self):
self.helper.layout = Layout(
IncludeEasyMDE(),
Field("title"),
- Field("description"),
Field("type"),
- Field("image"),
Field("introduction", css_class="md-editor preview-source"),
ButtonHolder(
StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn"),
@@ -163,19 +147,6 @@ def __init__(self, *args, **kwargs):
if "type" in self.initial:
self.helper["type"].wrap(Field, disabled=True)
- def clean(self):
- cleaned_data = super().clean()
- image = cleaned_data.get("image", None)
- if image is not None and image.size > settings.ZDS_APP["gallery"]["image_max_size"]:
- self._errors["image"] = self.error_class(
- [
- _("Votre logo est trop lourd, la limite autorisée est de {} Ko").format(
- settings.ZDS_APP["gallery"]["image_max_size"] / 1024
- )
- ]
- )
- return cleaned_data
-
class EditContentForm(ContentForm):
title = None
@@ -185,7 +156,6 @@ class EditContentForm(ContentForm):
def _create_layout(self):
self.helper.layout = Layout(
IncludeEasyMDE(),
- Field("image"),
Field("introduction", css_class="md-editor preview-source"),
StrictButton(_("Aperçu"), type="preview", name="preview", css_class="btn btn-grey preview-btn"),
HTML(
@@ -199,7 +169,6 @@ def _create_layout(self):
with text=form.conclusion.value %}{% endif %}'
),
Field("last_hash"),
- Field("subcategory", template="crispy/checkboxselectmultiple.html"),
Field("msg_commit"),
ButtonHolder(StrictButton("Valider", type="submit")),
)
diff --git a/zds/tutorialv2/urls/urls_contents.py b/zds/tutorialv2/urls/urls_contents.py
index cd9feff1f5..32cb55f869 100644
--- a/zds/tutorialv2/urls/urls_contents.py
+++ b/zds/tutorialv2/urls/urls_contents.py
@@ -10,6 +10,7 @@
EditTitle,
EditSubtitle,
)
+from zds.tutorialv2.views.thumbnail import EditThumbnailView
from zds.tutorialv2.views.display.container import ContainerValidationView
from zds.tutorialv2.views.display.content import ContentValidationView
from zds.tutorialv2.views.events import EventsList
@@ -215,16 +216,12 @@ def get_version_pages():
path("enlever-contributeur/
/", RemoveContributorFromContent.as_view(), name="remove-contributor"),
path("ajouter-auteur//", AddAuthorToContent.as_view(), name="add-author"),
path("enlever-auteur//", RemoveAuthorFromContent.as_view(), name="remove-author"),
- # Modify the title and subtitle
path("modifier-titre//", EditTitle.as_view(), name="edit-title"),
path("modifier-sous-titre//", EditSubtitle.as_view(), name="edit-subtitle"),
- # Modify the license
+ path("modifier-miniature//", EditThumbnailView.as_view(), name="edit-thumbnail"),
path("modifier-licence//", EditContentLicense.as_view(), name="edit-license"),
- # Modify the tags
path("modifier-tags//", EditTags.as_view(), name="edit-tags"),
- # Modify the canonical link
path("modifier-lien-canonique/", EditCanonicalLinkView.as_view(), name="edit-canonical-link"),
- # Modify the categories
path("modifier-categories//", EditCategoriesView.as_view(), name="edit-categories"),
# beta:
path("activer-beta///", ManageBetaContent.as_view(action="set"), name="set-beta"),
diff --git a/zds/tutorialv2/views/contents.py b/zds/tutorialv2/views/contents.py
index 6ba8e9f4c9..21c9380110 100644
--- a/zds/tutorialv2/views/contents.py
+++ b/zds/tutorialv2/views/contents.py
@@ -16,7 +16,6 @@
from django.utils.translation import gettext_lazy as _
from django.views.generic import DeleteView
-from zds.gallery.mixins import ImageCreateMixin, NotAnImage
from zds.gallery.models import Gallery
from zds.member.decorator import LoggedWithReadWriteHability
from zds.member.utils import get_bot_account
@@ -69,43 +68,26 @@ def get_context_data(self, **kwargs):
return context
def form_valid(self, form):
- # create the object:
- self.content = PublishableContent()
- self.content.title = form.cleaned_data["title"]
- self.content.description = form.cleaned_data["description"]
- self.content.type = form.cleaned_data["type"]
- self.content.licence = self.request.user.profile.licence # Use the preferred license of the user if it exists
- self.content.creation_date = datetime.now()
-
- gallery = Gallery.objects.create(
+ self.content = PublishableContent(
+ title=form.cleaned_data["title"],
+ type=form.cleaned_data["type"],
+ licence=self.request.user.profile.licence, # Use the preferred license of the user if it exists
+ creation_date=datetime.now(),
+ )
+
+ self.content.gallery = Gallery.objects.create(
title=self.content.title,
slug=slugify(self.content.title),
pubdate=datetime.now(),
)
- # create image:
- if "image" in self.request.FILES:
- mixin = ImageCreateMixin()
- mixin.gallery = gallery
- try:
- img = mixin.perform_create(str(_("Icône du contenu")), self.request.FILES["image"])
- except NotAnImage:
- form.add_error("image", _("Image invalide"))
- return super().form_invalid(form)
- img.pubdate = datetime.now()
- self.content.gallery = gallery
- self.content.save()
- if "image" in self.request.FILES:
- self.content.image = img
- self.content.save()
-
- # We need to save the content before changing its author list since it's a many-to-many relationship
- self.content.authors.add(self.request.user)
+ self.content.save() # Commit to database before updating relationships
+ # Update relationships
+ self.content.authors.add(self.request.user)
self.content.ensure_author_gallery()
- self.content.save()
- # create a new repo :
+ # Create a new git repository
init_new_repo(
self.content,
form.cleaned_data["introduction"],
@@ -160,26 +142,6 @@ def form_valid(self, form):
publishable.update_date = datetime.now()
- # update image
- if "image" in self.request.FILES:
- gallery_defaults = {
- "title": publishable.title,
- "slug": slugify(publishable.title),
- "pubdate": datetime.now(),
- }
- gallery, _created = Gallery.objects.get_or_create(pk=publishable.gallery.pk, defaults=gallery_defaults)
- mixin = ImageCreateMixin()
- mixin.gallery = gallery
- try:
- img = mixin.perform_create(str(_("Icône du contenu")), self.request.FILES["image"])
- except NotAnImage:
- form.add_error("image", _("Image invalide"))
- return super().form_invalid(form)
- img.pubdate = datetime.now()
- publishable.image = img
-
- publishable.save()
-
# now, update the versioned information
sha = versioned.repo_update_top_container(
publishable.title,
@@ -188,10 +150,7 @@ def form_valid(self, form):
form.cleaned_data["conclusion"],
form.cleaned_data["msg_commit"],
)
-
- # update relationships :
publishable.sha_draft = sha
-
publishable.save()
self.success_url = reverse("content:view", args=[publishable.pk, publishable.slug])
diff --git a/zds/tutorialv2/views/display/content.py b/zds/tutorialv2/views/display/content.py
index e70af96495..7f2ab0e384 100644
--- a/zds/tutorialv2/views/display/content.py
+++ b/zds/tutorialv2/views/display/content.py
@@ -40,6 +40,7 @@
)
from zds.tutorialv2.utils import last_participation_is_old, mark_read
from zds.tutorialv2.views.contents import EditTitleForm, EditSubtitleForm
+from zds.tutorialv2.views.thumbnail import EditThumbnailForm
from zds.tutorialv2.views.display.config import (
ConfigForContentDraftView,
ConfigForVersionView,
@@ -141,6 +142,7 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form_edit_title"] = EditTitleForm(self.versioned_object)
context["form_edit_subtitle"] = EditSubtitleForm(self.versioned_object)
+ context["form_edit_thumbnail"] = EditThumbnailForm(self.versioned_object)
context["display_config"] = ConfigForContentDraftView(self.request.user, self.object, self.versioned_object)
return context
diff --git a/zds/tutorialv2/views/thumbnail.py b/zds/tutorialv2/views/thumbnail.py
new file mode 100644
index 0000000000..17acb49b83
--- /dev/null
+++ b/zds/tutorialv2/views/thumbnail.py
@@ -0,0 +1,102 @@
+from datetime import datetime
+
+from crispy_forms.bootstrap import StrictButton
+from crispy_forms.helper import FormHelper
+from crispy_forms.layout import Layout, Field
+from django.conf import settings
+from django.contrib import messages
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.forms import forms, FileField
+from django.shortcuts import redirect
+from django.urls import reverse
+from django.utils.translation import gettext_lazy as _
+
+from zds.gallery.mixins import ImageCreateMixin, NotAnImage
+from zds.gallery.models import Gallery
+from zds.tutorialv2.mixins import SingleContentFormViewMixin
+from zds.tutorialv2.models.database import PublishableContent
+from zds.utils.uuslug_wrapper import slugify
+
+from zds.utils.validators import with_svg_validator
+
+
+class EditThumbnailForm(forms.Form):
+ image = FileField(
+ label=_("Sélectionnez la miniature de la publication (max. {} ko).").format(
+ settings.ZDS_APP["gallery"]["image_max_size"] // 1024
+ ),
+ validators=[with_svg_validator],
+ required=True,
+ error_messages={
+ "file_too_large": _("La miniature est trop lourde ; la limite autorisée est de {} ko").format(
+ settings.ZDS_APP["gallery"]["image_max_size"] // 1024
+ ),
+ },
+ )
+
+ def __init__(self, versioned_content, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ self.helper = FormHelper()
+ self.helper.form_method = "post"
+ self.helper.form_action = reverse("content:edit-thumbnail", kwargs={"pk": versioned_content.pk})
+ self.helper.form_id = "edit-thumbnail"
+ self.helper.form_class = "modal modal-flex"
+
+ self.helper.layout = Layout(
+ Field("image"),
+ StrictButton("Valider", type="submit"),
+ )
+
+ self.previous_page_url = reverse(
+ "content:view", kwargs={"pk": versioned_content.pk, "slug": versioned_content.slug}
+ )
+
+ def clean_image(self):
+ image = self.cleaned_data.get("image", None)
+ if image is not None and image.size > settings.ZDS_APP["gallery"]["image_max_size"]:
+ self.add_error("image", self.declared_fields["image"].error_messages["file_too_large"])
+ return self.cleaned_data
+
+
+class EditThumbnailView(LoginRequiredMixin, SingleContentFormViewMixin):
+ modal_form = True
+ model = PublishableContent
+ form_class = EditThumbnailForm
+ success_message = _("La miniature a bien été changée.")
+ error_messages = {"invalid_image": _("Image invalide")}
+
+ 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
+
+ # Get or create gallery
+ gallery_defaults = {
+ "title": publishable.title,
+ "slug": slugify(publishable.title),
+ "pubdate": datetime.now(),
+ }
+ gallery, _created = Gallery.objects.get_or_create(pk=publishable.gallery.pk, defaults=gallery_defaults)
+ publishable.gallery = gallery
+
+ # Create image
+ mixin = ImageCreateMixin()
+ mixin.gallery = gallery
+ try:
+ thumbnail = mixin.perform_create(str(_("Icône du contenu")), self.request.FILES["image"])
+ except NotAnImage:
+ form.add_error("image", self.error_messages["invalid_image"])
+ return super().form_invalid(form)
+ thumbnail.pubdate = datetime.now()
+
+ # Update thumbnail
+ publishable.image = thumbnail
+ publishable.save()
+
+ messages.success(self.request, self.success_message)
+
+ return redirect(form.previous_page_url)
From e9ffbd799446e93a38267131fe8423a56f381a36 Mon Sep 17 00:00:00 2001
From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com>
Date: Wed, 8 May 2024 22:11:03 +0200
Subject: [PATCH 3/7] Corrige les tests existants
---
.../tests/tests_views/tests_content.py | 9 -----
.../tests/tests_views/tests_published.py | 34 ++++++-------------
2 files changed, 10 insertions(+), 33 deletions(-)
diff --git a/zds/tutorialv2/tests/tests_views/tests_content.py b/zds/tutorialv2/tests/tests_views/tests_content.py
index cf7e9d31c3..14fe2429d9 100644
--- a/zds/tutorialv2/tests/tests_views/tests_content.py
+++ b/zds/tutorialv2/tests/tests_views/tests_content.py
@@ -232,8 +232,6 @@ def test_ensure_access(self):
def test_basic_tutorial_workflow(self):
"""General test on the basic workflow of a tutorial: creation, edition, deletion for the author"""
-
- # login with author
self.client.force_login(self.user_author)
# create tutorial
@@ -262,13 +260,10 @@ def test_basic_tutorial_workflow(self):
reverse("content:create-content", kwargs={"created_content_type": "TUTORIAL"}),
{
"title": title,
- "description": description,
"introduction": intro,
"conclusion": conclusion,
"type": "TUTORIAL",
"licence": self.licence.pk,
- "subcategory": self.subcategory.pk,
- "image": (settings.BASE_DIR / "fixtures" / "noir_black.png").open("rb"),
},
follow=False,
)
@@ -282,7 +277,6 @@ def test_basic_tutorial_workflow(self):
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())
- self.assertEqual(Image.objects.filter(gallery__pk=tuto.gallery.pk).count(), 1) # icon is uploaded
# access to tutorial
result = self.client.get(reverse("content:edit", args=[pk, slug]), follow=False)
@@ -308,14 +302,11 @@ def test_basic_tutorial_workflow(self):
"type": "TUTORIAL",
"subcategory": self.subcategory.pk,
"last_hash": versioned.compute_hash(),
- "image": (settings.BASE_DIR / "fixtures" / "logo.png").open("rb"),
},
follow=False,
)
self.assertEqual(result.status_code, 302)
- self.assertEqual(Image.objects.filter(gallery__pk=tuto.gallery.pk).count(), 2) # new icon is uploaded
-
tuto = PublishableContent.objects.get(pk=pk)
self.assertEqual(tuto.licence, None)
versioned = tuto.load_version()
diff --git a/zds/tutorialv2/tests/tests_views/tests_published.py b/zds/tutorialv2/tests/tests_views/tests_published.py
index 33da7aa20d..ee8cb71250 100644
--- a/zds/tutorialv2/tests/tests_views/tests_published.py
+++ b/zds/tutorialv2/tests/tests_views/tests_published.py
@@ -2044,37 +2044,25 @@ def test_social_cards_without_image(self):
self.check_images_socials(result, "static/images/tutorial-illu", self.chapter1.title, self.tuto.description)
def test_social_cards_with_image(self):
- """
- Check that all cards are produce for socials network
- """
- # connect with author:
+ """Check that all cards are produced for socials network"""
self.client.force_login(self.user_author)
- # add image to public tutorial
+
+ # Add image to public tutorial
self.client.post(
- reverse("content:edit", args=[self.tuto.pk, self.tuto.slug]),
- {
- "title": self.tuto.title,
- "description": self.tuto.description,
- "introduction": "",
- "conclusion": "",
- "type": "TUTORIAL",
- "licence": self.tuto.licence.pk,
- "subcategory": self.subcategory.pk,
- "last_hash": self.tuto.load_version().compute_hash(),
- "image": (settings.BASE_DIR / "fixtures" / "logo.png").open("rb"),
- },
+ reverse("content:edit-thumbnail", args=[self.tuto.pk]),
+ {"image": (settings.BASE_DIR / "fixtures" / "logo.png").open("rb")},
follow=True,
)
- # test access for guest user (bot of social network for example)
+ # Test as a guest (bot of social network for example)
self.client.logout()
- # check tutorial cards
+ # Check tutorial cards
result = self.client.get(reverse("tutorial:view", kwargs={"pk": self.tuto.pk, "slug": self.tuto.slug}))
self.assertEqual(result.status_code, 200)
-
self.check_images_socials(result, "media/galleries/", self.tuto.title, self.tuto.description)
- # check part cards
+
+ # Check part cards
result = self.client.get(
reverse(
"tutorial:view-container",
@@ -2082,10 +2070,9 @@ def test_social_cards_with_image(self):
)
)
self.assertEqual(result.status_code, 200)
-
self.check_images_socials(result, "media/galleries/", self.part1.title, self.tuto.description)
- # check chapter cards
+ # Check chapter cards
result = self.client.get(
reverse(
"tutorial:view-container",
@@ -2098,7 +2085,6 @@ def test_social_cards_with_image(self):
)
)
self.assertEqual(result.status_code, 200)
-
self.check_images_socials(result, "media/galleries/", self.chapter1.title, self.tuto.description)
def test_add_help_tuto(self):
From cbcadf000402a5bf70cbb4a06343bccc54dbd9c5 Mon Sep 17 00:00:00 2001
From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com>
Date: Thu, 9 May 2024 18:18:08 +0200
Subject: [PATCH 4/7] Ajoute des tests pour la vue des miniatures
---
.../tests_views/tests_editthumbnailview.py | 123 ++++++++++++++++++
1 file changed, 123 insertions(+)
create mode 100644 zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py
diff --git a/zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py b/zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py
new file mode 100644
index 0000000000..df3fdb9d4b
--- /dev/null
+++ b/zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py
@@ -0,0 +1,123 @@
+from datetime import datetime
+
+from django.conf import settings
+from django.test import TestCase
+from django.urls import reverse
+from django.utils.html import escape
+
+from zds.tutorialv2.tests import TutorialTestMixin, override_for_contents
+from zds.tutorialv2.tests.factories import PublishableContentFactory
+from zds.member.tests.factories import ProfileFactory, StaffProfileFactory
+from zds.tutorialv2.views.thumbnail import EditThumbnailForm, EditThumbnailView
+
+
+@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("content:edit-thumbnail", kwargs={"pk": self.content.pk})
+ thumbnail = (settings.BASE_DIR / "fixtures" / "logo.png").open("rb")
+ self.form_data = {"image": thumbnail}
+ 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 WorkflowTests(TutorialTestMixin, TestCase):
+ """Test the workflow of the form, such as validity errors and success messages."""
+
+ def setUp(self):
+ self.author = ProfileFactory()
+ self.content = PublishableContentFactory(author_list=[self.author.user])
+
+ # Get information to be reused in tests
+ self.form_url = reverse("content:edit-thumbnail", kwargs={"pk": self.content.pk})
+
+ self.form_error_messages = EditThumbnailForm.declared_fields["image"].error_messages
+ self.view_error_messages = EditThumbnailView.error_messages
+ self.success_message = EditThumbnailView.success_message
+
+ # Log in with an authorized user (e.g the author of the content)
+ self.client.force_login(self.author.user)
+
+ def get_test_cases(self):
+ good_thumbnail = (settings.BASE_DIR / "fixtures" / "logo.png").open("rb")
+ humongus_thumbnail = (settings.BASE_DIR / "fixtures" / "image_test.jpg").open("rb")
+ return {
+ "empty_form": {"inputs": {}, "expected_outputs": [self.form_error_messages["required"]]},
+ "empty_fields": {"inputs": {"image": ""}, "expected_outputs": [self.form_error_messages["required"]]},
+ "basic_success": {"inputs": {"image": good_thumbnail}, "expected_outputs": [self.success_message]},
+ "file_too_large": {
+ "inputs": {"image": humongus_thumbnail},
+ "expected_outputs": [self.form_error_messages["file_too_large"]],
+ },
+ }
+
+ def test_form_workflow(self):
+ test_cases = self.get_test_cases()
+ for case_name, case in test_cases.items():
+ with self.subTest(msg=case_name):
+ response = self.client.post(self.form_url, case["inputs"], follow=True)
+ for msg in case["expected_outputs"]:
+ self.assertContains(response, escape(msg))
+
+
+@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("content:edit-thumbnail", kwargs={"pk": self.content.pk})
+ self.form_data = {"image": (settings.BASE_DIR / "fixtures" / "logo.png").open("rb")}
+
+ self.client.force_login(self.author.user)
+
+ def test_normal(self):
+ self.assertEqual(self.content.title, self.content.gallery.title)
+ start_date = datetime.now()
+ self.assertTrue(self.content.update_date < start_date)
+
+ response = self.client.post(self.form_url, self.form_data, follow=True)
+ self.assertEqual(response.status_code, 200)
+
+ self.content.refresh_from_db()
+
+ self.assertIsNotNone(self.content.image)
+ self.assertEqual(self.content.gallery.get_images().count(), 1)
From 541225fdedd762b47ccc30add0b91623fa541e86 Mon Sep 17 00:00:00 2001
From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com>
Date: Sat, 20 Jul 2024 15:21:45 +0200
Subject: [PATCH 5/7] =?UTF-8?q?Ajoute=20l'inscription=20dans=20le=20journa?=
=?UTF-8?q?l=20d'=C3=A9v=C3=A9nement?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
templates/tutorialv2/events/descriptions.part.html | 3 +++
zds/tutorialv2/models/events.py | 11 +++++++++++
zds/tutorialv2/signals.py | 4 ++++
.../tests/tests_views/tests_editthumbnailview.py | 5 ++++-
zds/tutorialv2/views/thumbnail.py | 4 ++++
5 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/templates/tutorialv2/events/descriptions.part.html b/templates/tutorialv2/events/descriptions.part.html
index 360720290a..af203e3430 100644
--- a/templates/tutorialv2/events/descriptions.part.html
+++ b/templates/tutorialv2/events/descriptions.part.html
@@ -57,6 +57,9 @@
{% endif %}
+{% elif event.type == "thumbnail_management" %}
+ {{ event.performer }} a modifié la miniature du contenu.
+
{% elif event.type == "tags_management" %}
{{ event.performer }} a modifié les tags du contenu.
diff --git a/zds/tutorialv2/models/events.py b/zds/tutorialv2/models/events.py
index 0cfa001bc8..8617d105c1 100644
--- a/zds/tutorialv2/models/events.py
+++ b/zds/tutorialv2/models/events.py
@@ -13,6 +13,7 @@
from zds.tutorialv2.views.goals import EditGoals
from zds.tutorialv2.views.labels import EditLabels
from zds.tutorialv2.views.help import ChangeHelp
+from zds.tutorialv2.views.thumbnail import EditThumbnailView
from zds.tutorialv2.views.validations_contents import (
ReserveValidation,
AskValidationForContent,
@@ -48,6 +49,7 @@
signals.contributors_management: "contributors_management",
signals.beta_management: "beta_management",
signals.validation_management: "validation_management",
+ signals.thumbnail_management: "thumbnail_management",
signals.tags_management: "tags_management",
signals.canonical_link_management: "canonical_link_management",
signals.goals_management: "goals_management",
@@ -135,6 +137,15 @@ def record_event_validation_management(sender, performer, signal, content, versi
).save()
+@receiver(signals.thumbnail_management, sender=EditThumbnailView)
+def record_event_thumbnail_management(sender, performer, signal, content, **_):
+ Event.objects.create(
+ performer=performer,
+ type=types[signal],
+ content=content,
+ )
+
+
@receiver(signals.tags_management, sender=EditTags)
def record_event_tags_management(sender, performer, signal, content, **_):
Event(
diff --git a/zds/tutorialv2/signals.py b/zds/tutorialv2/signals.py
index 5815177dd6..f47dc535c6 100644
--- a/zds/tutorialv2/signals.py
+++ b/zds/tutorialv2/signals.py
@@ -30,6 +30,10 @@
# Action is either "request", "cancel", "accept", "reject", "revoke", "reserve" or "unreserve".
validation_management = Signal()
+# Thumbnail management
+# For the signal below, the arguments "performer" and "content" shall be provided.
+thumbnail_management = Signal()
+
# Tags management
# For the signal below, the arguments "performer" and "content" shall be provided.
tags_management = Signal()
diff --git a/zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py b/zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py
index df3fdb9d4b..0cf1dd8d28 100644
--- a/zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py
+++ b/zds/tutorialv2/tests/tests_views/tests_editthumbnailview.py
@@ -1,4 +1,5 @@
from datetime import datetime
+from unittest.mock import patch
from django.conf import settings
from django.test import TestCase
@@ -109,7 +110,8 @@ def setUp(self):
self.client.force_login(self.author.user)
- def test_normal(self):
+ @patch("zds.tutorialv2.signals.thumbnail_management")
+ def test_normal(self, thumbnail_management):
self.assertEqual(self.content.title, self.content.gallery.title)
start_date = datetime.now()
self.assertTrue(self.content.update_date < start_date)
@@ -121,3 +123,4 @@ def test_normal(self):
self.assertIsNotNone(self.content.image)
self.assertEqual(self.content.gallery.get_images().count(), 1)
+ self.assertEqual(thumbnail_management.send.call_count, 1)
diff --git a/zds/tutorialv2/views/thumbnail.py b/zds/tutorialv2/views/thumbnail.py
index 17acb49b83..d2374056fc 100644
--- a/zds/tutorialv2/views/thumbnail.py
+++ b/zds/tutorialv2/views/thumbnail.py
@@ -13,8 +13,10 @@
from zds.gallery.mixins import ImageCreateMixin, NotAnImage
from zds.gallery.models import Gallery
+from zds.tutorialv2 import signals
from zds.tutorialv2.mixins import SingleContentFormViewMixin
from zds.tutorialv2.models.database import PublishableContent
+from zds.utils import get_current_user
from zds.utils.uuslug_wrapper import slugify
from zds.utils.validators import with_svg_validator
@@ -99,4 +101,6 @@ def form_valid(self, form):
messages.success(self.request, self.success_message)
+ signals.thumbnail_management.send(sender=self.__class__, performer=get_current_user(), content=publishable)
+
return redirect(form.previous_page_url)
From e1f0696f7d7d768b8bae7a3f7737a37265ac05dc Mon Sep 17 00:00:00 2001
From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com>
Date: Tue, 10 Sep 2024 22:57:37 +0200
Subject: [PATCH 6/7] Change le style du bouton d'image
---
assets/scss/layout/_content.scss | 31 +++++++++++++++++++------------
1 file changed, 19 insertions(+), 12 deletions(-)
diff --git a/assets/scss/layout/_content.scss b/assets/scss/layout/_content.scss
index 4ff0ea84e0..e67f4372b2 100644
--- a/assets/scss/layout/_content.scss
+++ b/assets/scss/layout/_content.scss
@@ -39,38 +39,45 @@
box-sizing: border-box;
}
- &:hover>.edit-thumbnail {
+ >.edit-thumbnail {
display: block;
- width: 100%;
- height: 100%;
-
+ width: $length-32;
+ height: $length-32;
+ margin-top: calc($length-64/2 - $length-32/2);
+ margin-left: calc($length-64/2 - $length-32/2);
+ border-radius: $radius-round;
background-color: rgba($grey-200, 0.7);
-
&.with-thumbnail {
position: relative;
top: -$length-64;
}
-
&.with-placeholder{
position: relative;
top: 0;
}
-
&::after {
content: " ";
-
display: block;
width: $length-16;
height: $length-16;
-
position: relative;
- top: calc($length-64/2 - $length-16/2);
- left: calc($length-64/2 - $length-16/2);
-
+ top: calc($length-32/2 - $length-16/2);
+ left: calc($length-32/2 - $length-16/2);
@include sprite;
@include sprite-position($edit-blue);
background-repeat: no-repeat;
}
+ transition: border-radius .005s ease-in-out;
+ &:hover {
+ border-radius: 0;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ &::after {
+ top: calc($length-64/2 - $length-16/2);
+ left: calc($length-64/2 - $length-16/2);
+ }
+ }
}
&.article-illu {
From b34af46166f42beded7b605a8f0c6c17cdac8c74 Mon Sep 17 00:00:00 2001
From: Arnaud-D <35631001+Arnaud-D@users.noreply.github.com>
Date: Sun, 15 Sep 2024 20:03:19 +0200
Subject: [PATCH 7/7] =?UTF-8?q?Cache=20l'image=20par=20d=C3=A9faut=20pour?=
=?UTF-8?q?=20les=20fonds=20transparents?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
templates/tutorialv2/includes/headline/title.part.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/tutorialv2/includes/headline/title.part.html b/templates/tutorialv2/includes/headline/title.part.html
index f2c84c898a..8ec2bfdeac 100644
--- a/templates/tutorialv2/includes/headline/title.part.html
+++ b/templates/tutorialv2/includes/headline/title.part.html
@@ -6,7 +6,7 @@
{% if show_thumbnail %}
-
+
{% if content.image %}
{% endif %}